Java Concurrency Locking Mechanisms
In Java concurrent programming, locking mechanisms are crucial for ensuring thread safety. Their primary function is to guarantee that only one thread accesses a protected resource at any given time, preventing data inconsistency and concurrent conflicts.
1. Built-in Synchronized Locks
Java's built-in lock is implemented via the synchronized keyword, which can modify methods or code blocks to ensure exclusive execution.
public class SyncCounter {
private int value = 0;
public synchronized void add() {
value++;
}
public int getValue() {
return value;
}
public static void main(String[] args) throws InterruptedException {
SyncCounter counter = new SyncCounter();
Runnable job = () -> {
for (int i = 0; i < 1000; i++) {
counter.add();
}
};
Thread worker1 = new Thread(job);
Thread worker2 = new Thread(job);
worker1.start();
worker2.start();
worker1.join();
worker2.join();
System.out.println("Result: " + counter.getValue());
}
}
2. ReentrantLock
ReentrantLock from the java.util.concurrent.locks package provides a more flexible locking mechanism then synchronized, supporting features like fair locking and non-blocking acquisition attempts.
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockCounter {
private int total = 0;
private final Lock lock = new ReentrantLock();
public void increase() {
lock.lock();
try {
total++;
} finally {
lock.unlock();
}
}
public int getTotal() {
return total;
}
public static void main(String[] args) throws InterruptedException {
LockCounter counter = new LockCounter();
Runnable task = () -> {
for (int j = 0; j < 1000; j++) {
counter.increase();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final total: " + counter.getTotal());
}
}
3. ReadWriteLock
ReadWriteLock allows multiple threads to read concurrently while granting exclusive access for writing, which can significantly improve performance in read-heavy scenarios.
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class RWLCounter {
private int number = 0;
private final ReadWriteLock rwl = new ReentrantReadWriteLock();
public void increment() {
rwl.writeLock().lock();
try {
number++;
} finally {
rwl.writeLock().unlock();
}
}
public int readNumber() {
rwl.readLock().lock();
try {
return number;
} finally {
rwl.readLock().unlock();
}
}
public static void main(String[] args) throws InterruptedException {
RWLCounter demo = new RWLCounter();
Runnable write = () -> {
for (int k = 0; k < 1000; k++) {
demo.increment();
}
};
Runnable read = () -> {
for (int k = 0; k < 1000; k++) {
System.out.println("Current: " + demo.readNumber());
}
};
Thread writer = new Thread(write);
Thread reader1 = new Thread(read);
Thread reader2 = new Thread(read);
writer.start();
reader1.start();
reader2.start();
writer.join();
reader1.join();
reader2.join();
System.out.println("End value: " + demo.readNumber());
}
}
4. Condition Variables with Locks
ReentrantLock provides Condition objects for complex thread cooordination, allowing threads to wait until a specific condition is met.
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionExample {
private final Lock lock = new ReentrantLock();
private final Condition cond = lock.newCondition();
private boolean flag = false;
public void awaitSignal() {
lock.lock();
try {
while (!flag) {
cond.await();
}
System.out.println("Condition satisfied, continuing...");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public void triggerSignal() {
lock.lock();
try {
flag = true;
cond.signalAll();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ConditionExample example = new ConditionExample();
Thread waiter = new Thread(example::awaitSignal);
Thread signaller = new Thread(example::triggerSignal);
waiter.start();
Thread.sleep(1000);
signaller.start();
waiter.join();
signaller.join();
}
}
5. StampedLock
Introduced in Java 8, StampedLock offers a efficient read-write lock with optimistic read support, using stamps to manage lock state.
import java.util.concurrent.locks.StampedLock;
public class StampedExample {
private int counter = 0;
private final StampedLock slock = new StampedLock();
public void bump() {
long stamp = slock.writeLock();
try {
counter++;
} finally {
slock.unlockWrite(stamp);
}
}
public int fetch() {
long stamp = slock.tryOptimisticRead();
int local = counter;
if (!slock.validate(stamp)) {
stamp = slock.readLock();
try {
local = counter;
} finally {
slock.unlockRead(stamp);
}
}
return local;
}
public static void main(String[] args) throws InterruptedException {
StampedExample se = new StampedExample();
Runnable writeJob = () -> {
for (int m = 0; m < 1000; m++) {
se.bump();
}
};
Runnable readJob = () -> {
for (int m = 0; m < 1000; m++) {
System.out.println("Value: " + se.fetch());
}
};
Thread writerThread = new Thread(writeJob);
Thread readerThreadA = new Thread(readJob);
Thread readerThreadB = new Thread(readJob);
writerThread.start();
readerThreadA.start();
readerThreadB.start();
writerThread.join();
readerThreadA.join();
readerThreadB.join();
System.out.println("Final counter: " + se.fetch());
}
}