Thread-safe cache using a linked list
Thread safety
std::malloc and std::free are thread-safe by themselves... But Freelist and Cache aren't, unless exclusively accessed through allocate or deallocate.
In all other cases some synchronization is needed in FreeList::push and FreeList::pop (or alternatively, Cache::allocate and Cache::deallocate).

There are some options to make those two classes thread-safe for all access paths:

Move get, get_cache, Cache and FreeList into a class as private nested classes / member functions (so they aren't publicly accessible anymore) and make allocate and deallocate a friend of that class. (private/anonymous namespace won't work since that one has to ve just as accessible as allocate/deallocate because it has to reside in a header.)
Why move get and get_cache? Because references and pointers to thread_local objects can be shared with other threads - so a threads might get access to another threads thread_local Cache inside get.
thread_local objects are only thread-safe if no reference or pointer to them gets shared with other threads (which might happen accidentally, e.g. by a mismanaged lambda capture).
This means allocate and deallocate are not thread-safe as long as references to the underlying Cache (via get or get_cache) could be shared by someone else. And since allocate and deallocate have exactly the same level of access as get or get_cache the posted implementation is not thread-safe in the general case.

Add some locking inside FreeList, or make FreeList lock-free by making this_head a std::atomic&lt;Node*&gt;.
This would make the critical section inside Cache::allocate and Cache::deallocate thread-safe as well.



Memory management

Cache::allocate won't provide correctly aligned memory for alignments greater than std::max_align_t (std::malloc is only specified to support alignments to this value). This might cause problems if higher alignments are needed, e.g. for SSE or AVX instructions.
std::aligned_alloc could be used as a replacement. (Beware: std::aligned_alloc isn't available for MSVC yet.)

It isn't possible to create objects whose size is less than sizeof(void*) - so exchanging int64_t for int32_t inside main will fail to compile on 64-bit systems. This could be fixed by always allocating chunks of memory that are at least sizeof(void*) (or FreeList::NODE_SIZE, if you prefer).


Naming

Cache doesn't actually cache objects - only chunks of memory. FreeListAllocator might be a better name.

get is a very generic name. (get_)thread_local_instance or singleton might be more descriptive.

Similar, get_cache might better be get_allocator or get_thread_local_allocator.


General stuff

With some slight modifications Cache could be changed into a standard library compatible std::pmr::memory_resource - which would allow it to be used with standard containers.

If I had to design this, I'd likely make FreeList an adaptor over another allocator (taken as template parameter) who would be asked for memory if the list was empty. This design would allow easy composition of different allocation strategies. There's an excellent talk by Andrei Alexandrescu about this topic.