In concurrent programming, locking is used to control access to shared resources to prevent data corruption and race conditions. Two common locking strategies are optimistic locking and pessimistic locking.
Optimistic locking is a strategy where a thread assumes that the shared resource is not currently being accessed by any other thread and performs its operation. If the assumption is correct, the operation succeeds, and if it is not, the thread will retry the operation with a fresh copy of the shared resource. This strategy is useful when the probability of a collision is low and conflicts are rare. Optimistic locking minimizes lock contention and can improve performance but may increase retry overhead.
Pessimistic locking, on the other hand, is a strategy where a thread acquires a lock on the shared resource before performing an operation. This strategy is useful when there is a high probability of a collision, and conflicts are frequent. Pessimistic locking ensures mutual exclusion and eliminates the possibility of race conditions but can increase lock contention and reduce concurrency.
Here’s an example of optimistic locking in Java using the AtomicInteger class:
AtomicInteger counter = new AtomicInteger(0);
// ...
int oldValue = counter.get();
int newValue = oldValue + 1;
while (!counter.compareAndSet(oldValue, newValue)) {
oldValue = counter.get();
newValue = oldValue + 1;
}
In this example, we’re using an AtomicInteger to implement a counter that can be safely accessed by multiple threads. The get() method returns the current value of the counter, and the compareAndSet() method atomically compares the current value with the expected value and updates the counter’s value if they are the same. The while loop keeps retrying until the update succeeds.
Here’s an example of pessimistic locking in Java using the synchronized keyword:
private Object lock = new Object();
private int counter = 0;
// ...
synchronized(lock) {
counter++;
}
In this example, we’re using the synchronized keyword to acquire a lock on the lock object before incrementing the counter. Only one thread can hold the lock at a time, ensuring mutual exclusion and preventing race conditions.
In general, optimistic locking is more suitable for low-contention scenarios with infrequent conflicts, while pessimistic locking is more suitable for high-contention scenarios with frequent conflicts. The choice of locking strategy depends on the specific use case and the trade-offs between concurrency and lock contention.