JPA - Using Pessimistic Locking

[Updated: Dec 22, 2017, Created: Dec 21, 2017]

Optimistic locking assumes (as being optimistic) that there will be rare chances of multi users read/write conflicts, so it delays the conflict checking till the commit time, whereas pessimistic assumes that there is a high possibility of conflict and acquires a database lock at begging of the transaction.

In following example we will learn how to implement pessimistic locking by using LockModeType.PESSIMISTIC_READ and LockModeType.PESSIMISTIC_WRITE.

A transaction involving LockModeType.PESSIMISTIC_READ is an intent to just read the entity without modifying it, whereas LockModeType.PESSIMISTIC_WRITE transaction may modify the entity.

A transaction that has acquired PESSIMISTIC_READ lock, prevents any other transaction from acquiring a PESSIMISTIC_WRITE. Multiple PESSIMISTIC_READs can still happen simultaneously, that's the reason it is also known as shared lock.

A transaction that has acquired PESSIMISTIC_WRITE lock, prevents any other transaction from acquiring either of PESSIMISTIC_WRITE or PESSIMISTIC_READ lock. It is also known as exclusive lock.

Example

The Entity

@Entity
public class Article {
  @Id
  @GeneratedValue
  private long id;
  private String content;
    .............
}

Pessimistic write blocking pessimistic read example

In following example, we are going to use threads to simulate two users. First thread will obtain PESSIMISTIC_WRITE lock and will update the Article entity, during that time another thread will try to obtain PESSIMISTIC_READ lock and will block (or it may fail with exception, depending on underlying locking timeout value) till the first transaction is committed:

public class PessimisticLockExample {
  private static EntityManagerFactory entityManagerFactory =
          Persistence.createEntityManagerFactory("example-unit");

  public static void main(String[] args) throws Exception {
      ExecutorService es = Executors.newFixedThreadPool(3);
      try {
          persistArticle();
          es.execute(() -> {
              updateArticle();
          });
          es.execute(() -> {
              //simulating other user by using different thread
              readArticle();
          });
          es.shutdown();
          es.awaitTermination(1, TimeUnit.MINUTES);
      } finally {
          entityManagerFactory.close();
      }
  }

  private static void updateArticle() {
      log("updating Article entity");
      EntityManager em = entityManagerFactory.createEntityManager();
      em.getTransaction().begin();
      Article article = em.find(Article.class, 1L, LockModeType.PESSIMISTIC_WRITE);
      try {
          TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      article.setContent("updated content .. ");
      log("committing in write thread.");
      em.getTransaction().commit();
      em.close();
      log("Article updated", article);
  }

  private static void readArticle() {
      try {//some delay before reading
          TimeUnit.MILLISECONDS.sleep(100);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      log("before acquiring read lock");
      EntityManager em = entityManagerFactory.createEntityManager();
      em.getTransaction().begin();
      Article article = em.find(Article.class, 1L, LockModeType.PESSIMISTIC_READ);
      log("After acquiring read lock", article);
      em.getTransaction().commit();
      em.close();
      log("Article after read commit", article);
  }

  public static void persistArticle() {
      log("persisting article");
      Article article = new Article("test article");
      EntityManager em = entityManagerFactory.createEntityManager();
      em.getTransaction().begin();
      em.persist(article);
      em.getTransaction().commit();
      em.close();
      log("Article persisted", article);
  }

  private static void log(Object... msgs) {
      System.out.println(LocalTime.now() + " - " + Thread.currentThread().getName() +
              " - " + Arrays.toString(msgs));
  }
}
22:39:33.330 - main - [persisting article]
22:39:33.413 - main - [Article persisted, Article{id=1, content='test article'}]
22:39:33.414 - pool-2-thread-1 - [updating Article entity]
22:39:33.514 - pool-2-thread-2 - [before acquiring read lock]
22:39:35.423 - pool-2-thread-1 - [committing in write thread.]
22:39:35.428 - pool-2-thread-1 - [Article updated, Article{id=1, content='updated content .. '}]
22:39:35.435 - pool-2-thread-2 - [After acquiring read lock, Article{id=1, content='updated content .. '}]
22:39:35.436 - pool-2-thread-2 - [Article after read commit, Article{id=1, content='updated content .. '}]

As seen in above output, read thread blocked till the commit time of the write transaction.

Pessimistic read blocking pessimistic write example

This example reads Article entity first with PESSIMISTIC_READ lock mode, hence the update thread (PESSIMISTIC_WRITE mode) blocks till the read transaction finishes.

public class PessimisticLockExample2 {
  private static EntityManagerFactory entityManagerFactory =
          Persistence.createEntityManagerFactory("example-unit");

  public static void main(String[] args) throws Exception {
      ExecutorService es = Executors.newFixedThreadPool(3);
      try {
          persistArticle();
          es.execute(() -> {
              //simulating other user read by using different thread
              readArticle();
          });
          es.execute(() -> {
              updateArticle();
          });

          es.shutdown();
          es.awaitTermination(1, TimeUnit.MINUTES);
      } finally {
          entityManagerFactory.close();
      }
  }

  private static void updateArticle() {
      try {//some delay before writing
          TimeUnit.MILLISECONDS.sleep(100);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      EntityManager em = entityManagerFactory.createEntityManager();
      em.getTransaction().begin();
      log("write thread before acquiring lock");
      Article article = em.find(Article.class, 1L, LockModeType.PESSIMISTIC_WRITE);
      log("write thread after acquiring lock");
      article.setContent("updated content .. ");
      log("committing in write thread.");
      em.getTransaction().commit();
      em.close();
      log("Article updated", article);
  }

  private static void readArticle() {
      log("before acquiring read lock");
      EntityManager em = entityManagerFactory.createEntityManager();
      em.getTransaction().begin();
      Article article = em.find(Article.class, 1L, LockModeType.PESSIMISTIC_READ);
      log("After acquiring read lock", article);
      try {
          TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      em.getTransaction().commit();
      em.close();
      log("Article after read commit", article);
  }
    .............
}
22:40:57.654 - main - [persisting article]
22:40:57.742 - main - [Article persisted, Article{id=1, content='test article'}]
22:40:57.743 - pool-2-thread-1 - [before acquiring read lock]
22:40:57.754 - pool-2-thread-1 - [After acquiring read lock, Article{id=1, content='test article'}]
22:40:57.844 - pool-2-thread-2 - [write thread before acquiring lock]
22:40:59.755 - pool-2-thread-1 - [Article after read commit, Article{id=1, content='test article'}]
22:40:59.759 - pool-2-thread-2 - [write thread after acquiring lock]
22:40:59.759 - pool-2-thread-2 - [committing in write thread.]
22:40:59.764 - pool-2-thread-2 - [Article updated, Article{id=1, content='updated content .. '}]

Example Project

Dependencies and Technologies Used :

  • h2 1.4.196: H2 Database Engine.
  • hibernate-core 5.2.12.Final: The core O/RM functionality as provided by Hibernate.
  • JDK 1.8
  • Maven 3.3.9

Pessimistic Lock Example Select All Download
  • pessimistic-lock-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
        • resources
          • META-INF

See Also