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 ProjectDependencies 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.
Implements javax.persistence:javax.persistence-api version 2.1 - JDK 1.8
- Maven 3.3.9
|