Yes, synchronized is the way to go.
I think the synchronization has to be at the connection level. For example, HiveStatement also needs to make calls on the thrift interface. It's not just DatabaseMetaData.
So we should add a new data member to HiveConnection:
Object connectionMutex = new Object();
Then pass connectionMutex to constructors of sub-objects which need to participate in synchronization. They can then do
around their critical sections.
Creating a separate object for this purpose allows us to keep control over synchronization (e.g. so it doesn't get mixed up with user-level or thrift-level synchronization code later). We'll also need to be able to skip synchronization in the case of asynchronous cancel, but that's a separate task.
We should also review to see if there is any client-side state which needs protection.