Optimistic Locking and Pessimistic Locking in Java
In Java (and generally in database systems), Optimistic Locking and Pessimistic Locking are two concurrency control strategies used to manage access to shared resources in multi-threaded or multi-user environments, such as database records or shared memory. Both approaches aim to ensure data integrity and prevent conflicts, but they take different approaches to handling potential conflicts. Here's a breakdown of both:
1. Optimistic Locking
Optimistic locking assumes that multiple transactions or threads can complete without interfering with each other. Rather than locking a resource for the entire duration of an operation, it allows concurrent access and checks for conflicts only when the resource is about to be modified. If a conflict is detected, it will retry or fail the operation.
How Optimistic Locking Works:
- No initial locking: The system does not lock the resource upfront. Instead, multiple threads can read and work on the same resource.
- Version check: Each resource (e.g., a database row) is typically associated with a version number or a timestamp.
- Conflict detection: Before updating the resource, the system checks whether the resource has been modified by another transaction/thread since it was read. This is usually done by comparing the version number or timestamp.
- If the version matches (no change), the update proceeds, and the version is incremented.
- If the version doesn't match (resource modified), the update fails, and the transaction is typically retried.
Example:
@Entity
public class Product {
@Id
private Long id;
@Version
private int version; // Used for optimistic locking
private String name;
private double price;
// Getters and setters
}
In this example, the @Version
annotation in JPA (Java Persistence API) ensures that the version
field is used to check whether a record has been updated by another transaction before making changes.
When to Use Optimistic Locking:
- When conflicts are rare: Optimistic locking is more efficient in scenarios where collisions between transactions or threads are uncommon.
- Read-heavy environments: It’s suitable when there are many reads and fewer updates, as it allows concurrent access without locking resources for long periods.
- Non-blocking: Since it doesn’t lock resources initially, other threads can still access and work with the same data.
Pros of Optimistic Locking:
- Higher throughput and concurrency since it avoids locking the resource.
- No deadlocks, as it doesn't involve resource locks.
- More efficient in systems with fewer write conflicts.
Cons of Optimistic Locking:
- Can lead to more retries if there are frequent conflicts.
- More complex to handle in case of conflicts, especially if the system has to retry failed transactions.
2. Pessimistic Locking
Pessimistic locking assumes that conflicts between transactions or threads are likely, so it locks the resource for the entire duration of the operation. This ensures that only one transaction or thread can access or modify the resource at a time.
How Pessimistic Locking Works:
- Lock acquisition: When a transaction or thread accesses a resource, it locks the resource (or part of it) to prevent others from accessing it until the operation is complete.
- Lock release: The lock is held until the transaction or thread completes (success or failure), and then the lock is released, allowing others to access the resource.
- Exclusive access: Other threads or transactions attempting to access the locked resource must wait until the lock is released.
Example:
In JPA, you can use LockModeType.PESSIMISTIC_WRITE
or LockModeType.PESSIMISTIC_READ
to implement pessimistic locking.
EntityManager em = entityManagerFactory.createEntityManager();
Product product = em.find(Product.class, productId, LockModeType.PESSIMISTIC_WRITE);
product.setPrice(200.0);
em.getTransaction().commit();
In this example, the find()
method with LockModeType.PESSIMISTIC_WRITE
ensures that the Product
entity is locked for writing until the transaction is completed.
When to Use Pessimistic Locking:
- When conflicts are frequent: Pessimistic locking is suitable in environments where data contention is high, and multiple transactions are likely to modify the same resource concurrently.
- Write-heavy environments: It is often used when the system has many writes and updates.
- Avoiding race conditions: It’s effective in preventing race conditions where one thread modifies data while another thread is still reading or writing it.
Pros of Pessimistic Locking:
- Guarantees that no conflicts will occur because only one transaction/thread can access the resource at a time.
- Prevents the need for retries or conflict resolution logic.
- Ensures data integrity in high-contention environments.
Cons of Pessimistic Locking:
- Lower concurrency and throughput due to locking, which blocks other threads/transactions from accessing the resource.
- Can lead to deadlocks, where two or more threads/transactions are waiting on each other to release locks.
- Higher resource usage and reduced performance in read-heavy systems.
Key Differences Between Optimistic and Pessimistic Locking:
Aspect | Optimistic Locking | Pessimistic Locking |
---|---|---|
Concurrency | Higher, as no locks are held initially | Lower, as resources are locked, blocking other transactions |
Conflict Handling | Detects conflicts at the end, requires retries | Prevents conflicts upfront by locking the resource |
Usage | Suitable for read-heavy environments with few conflicts | Suitable for write-heavy environments with frequent conflicts |
Performance | Higher performance in low-contention scenarios | Lower performance due to locking overhead |
Deadlock Risk | No risk of deadlocks | Risk of deadlocks due to multiple locks |
Conclusion:
- Optimistic Locking is ideal when conflicts are rare, allowing multiple threads or transactions to work on the same data concurrently, with conflict detection happening only at the time of update.
- Pessimistic Locking is best when contention is high, preventing conflicts by locking the resource early in the process to ensure that only one transaction can modify the resource at a time.