Sharing Hibernate entities between threads

A few weeks ago I had a discussion with another developer about sharing entities (mapped by Hibernate) between threads. My personal opinion is that concurrency control should be localized to a few special objects (something we agreed upon). And most other objects don’t need any form of concurrency because one of the following rules applies:

  1. they are ‘immutable’. Safely created immutable objects are thread safe, eg a stateless service like a FireEmployeeService.
  2. they are confined to a single thread.

Entities are not immutable (in most cases), so the first rule doesn’t apply. Luckily the second rule applies in most standard applications: entities are created and used by a single thread (the concurrency control task has shifted to the database).

But as soon as an entity is transferred from one thread to another, the second rule doesn’t apply anymore, and you have to deal with concurrency control. This even applies to entities that are completely moved to another thread and not used by the original thread anymore!

So what can go wrong?

The most obvious thing that can go wrong is that the same object is used by multiple threads (the thread that created the object and the thread that received it). If this happens you can get all kinds of race problems.

Another thing that can go wrong are session related problems. If an entity has unloaded lazy loaded fields, it could happen that multiple threads are accessing the same Hibernate session.

object1@session1 transfered to thread-a
object2@session1 transfered to thread-b

If object1 and object2 both have a lazy loaded field, it could happen that thread-a and thread-b both are using the same session to load those fields. This is a bad thing because the Hibernate sessions are not thread safe. The safest thing you can do is evict the entity from the session before passing it to the other thread, so all references to the session are removed. It is up to the receiving thread, to decide if the object needs to be reattached.

The last thing that comes to mind are visibility problems: values written by one thread don’t have to be visible in other threads. Entities normally don’t have synchronized methods or volatile properties, so a different mechanism needs to be used to prevent visibility problems. Luckely with the new memory model, it is now perfectly clear which alternatives are available. A structure that posses the save handoff property (like a BlockingQueue) can be used to safely pass objects with visibility problems from one thread to another. It is important to realize that not just the entities could have visibility problems, the same problems can occur within the hibernate session. The session is not meant to be used by multiple threads, and therefor it has no reason to prevent visibility problems.

Conclusion

My advice is that sharing objects between threads should not be thought of lightly; you really needs to know what you are doing. This is even more true for Hibernate entities because there is a lot going on behind the screens.

Advertisements

6 Responses to Sharing Hibernate entities between threads

  1. Meindert says:

    I was just about to email you with a question about this. Thanks for the blog (and google)!

  2. sbrattla says:

    Would it not solve the problem if one synchronizes the mutator and accessor methods on the Hibernate entities?

    • pveentjer says:

      Hi sbrattla,

      a very good question.

      Take a look at the following example:

      class Account{
          private int balance;
      
         public synchronized int getBalance(){return balance;}
      
         public synchronized void setBalance(int newBalance){this.balance = newBalance;}
      }
      

      The logic that makes use of these methods still has problems:

      static void transfer(Account from, Account to, int amount){
          if(from.getBalance()<amount)
              throw new NotEnoughMoneyException();
      
          from.setBalance(from.getBalance()-amount);
          to.setBalance(to.getBalance()+amount);
      }
      

      This code is full of race problems:
      -it could happen that someone changes the from acount after the check is done
      -it could happen that someone changes the from account between the from.get and from.set
      -It could happen that somone changes the to account between the to.get and to.set

      Of course you could lock the from and to account before you begin:

      static void transfer(Account from, Account to, amount){
          synchronized(from){
              synhronized(to){
                    if(from.getBalance()<amount)
                          throw new NotEnoughMoneyException();
      
                   from.setBalance(from.getBalance()-amount);
                   to.setBalance(to.getBalance()+amount);
                }
          }
      }
      

      But this introduces possible liveness problems (deadlock).Imagine what happens when someone transfers money from account a to account b, and someone else transfers money from account b to account a.

      So just sprinkling synchronized over your system, will not make it threadsafe.

      And you still need to worry about the possible concurrent usage of the session to load lazy instances. The hibernate session is not threadsafe, so even if you have solved it on the objects, there still are possible hidden issues.

      So only do it if you really know what you are doing. But I would let the database handle all the concurrency issues for me because it prevents issues (if correctly used) over multiple jvm’s and java synchronization is not able to do this (unless you are using something like terracotta).

      • sbrattla says:

        Indeed. I guess a rather simple solution to the problem could be a combination of database transactions and (Hibernate) versioning. Hibernate supports version control of entities (using either a timestamp or version number), so i guess the transfer() method from your example could safely be implemented by making sure that the changes will be commited to the database only if both accounts have not been changed by anyone else?

  3. pveentjer says:

    As long as the objects are not shared between threads/transaction, the databases is indeed the simplest approach for dealing with concurrency issues.

    So even though it could be that the same entity (record) in the database has multiple object instances representing that entity, you don’t need to worry about concurrency control in Java memory. Just configure the transactions correctly and you are done with most of the problems (which is still a very complex task)

    But this only is true if the objects themselves are not shared between threads, and this should only be done if you know what you are doing.

  4. adavey says:

    I wish there was more information on ways to use threads with Hibernate.

    Our app has to deploy to a Sparc T5240. Crap single thread performance (really crap) but support for 128 hardware threads (great parallelism). Not my choice of hardware.

    Our application has an operation that on my development machine (i5) takes approx 3 seconds, but on the Sparc up to 240 seconds.

    We were able to get that down to 15 seconds by loading in the entire graph of objects needed for the operation and then splitting the operation up into little threads using the ForkJoin library (JSR166).

    But it needed a lot of testing. You don’t want the graph (a collection of collections of collections) to accidently trigger a Lazy Load.

    If hibernate had a way of creating a thread safe session that would be great.

    Another thing that would be tops (especially on this hardware) is the ability to populate a single persistence context with queries that execute on separate threads. If I had that I think I could get the time down to around 10 seconds.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: