Add additional safety checks to FFI Pointer handling

This makes sure we check if a pointer is NULL before dereferencing it
and also makes sure that when we free or finalize the object, we set the
internal pointer to NULL. That way we get an exception if it's used
afterwards.
Fix deadlocks in rb_thread_create

This was causing a deadlock because of two reasons. One is that we
didn't notify any joined threads when a thread created with
rb_thread_create terminated.

The second issue was that we didn't unlock the thread object and didn't
update alive status properly for the thread. Fixing this exposed another
problem. This problem happens because unlocking can cause a GC to run,
so it could GC objects put into a void* passed to the thread
initializer.

By making sure we also lock / unlock after starting the thread, we make
sure the frame calling rb_thread_create waits until the thread actually
runs. This makes sure objects with C-API handles in that frame don't get
collected and the new thread doesn't see invalid data in the void*
argument.