Locks

Locks are more sophisticated than synchronized blocks or methods.

ReentrantLock

ReentrantLock is reentrant, meaning that a thread can lock the same lock multiple times. This is different from the synchronized keyword, which would cause a thread to deadlock if it tried to re-enter a synchronized block it already holds. It's useful when it's not easy to keep track of whether you've already acquired a lock.

import java.util.concurrent.locks.ReentrantLock;

public class Main {
    
    private static final ReentrantLock lock = new ReentrantLock();
    private static int result;

    public static void fib(int num) {
        System.out.println("lock " + num);
        lock.lock();
        if (num == 1 || num == 2) {
            result += 1;
        } else {
            fib(num - 1);
            fib(num - 2);
        }
        System.out.println("unlock " + num);
        lock.unlock();
    }

    public static void main(String[] args) {
        result = 0;
        fib(5);
        System.out.println(result);
    }
}

Expected output:

lock 5
lock 4
lock 3
lock 2
unlock 2
lock 1
unlock 1
unlock 3
lock 2
unlock 2
unlock 4
lock 3
lock 2
unlock 2
lock 1
unlock 1
unlock 3
unlock 5
5

ReadWriteLock

ReadWriteLock allows multiple threads to acquire read lock concurrently but ensures that only one thread can write at a time.

There is also ReentrantReadWriteLock.

ReadWriteLock rwLock = new ReentrantReadWriteLock();
rwLock.readLock().lock();
rwLock.readLock().unlock();

StampedLock

StampedLock provides optimistic read lock beside read and write lock.

Optimistic Read Lock tryOptimisticRead() Allows threads to optimistically read data without blocking, and later verify whether the read was successful or invalidated by a write.

This reduces contention and improves throughput when writes are infrequent.

import java.util.concurrent.locks.StampedLock;

class Counter {
    private int count = 0;
    private final StampedLock lock = new StampedLock();

    public void increment() {
        long stamp = lock.writeLock();
        count++;
        lock.unlockWrite(stamp);
        System.out.println("incremented: " + count);
    }

    public int getCount() {
        long stamp = lock.tryOptimisticRead();
        int currentCount = count;
        if (!lock.validate(stamp)) {
            System.out.println("invalid read, retry");
            stamp = lock.readLock();
            currentCount = count;
            lock.unlockRead(stamp);
        }
        return currentCount;
    }
}

public class Main {

    public static void main(String[] args) {
        Counter counter = new Counter();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                counter.increment();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Read Count: " + counter.getCount());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }).start();
    }
}

Expected output:

incremented: 1
Read Count: 1
incremented: 2
Read Count: 2
incremented: 3
invalid read, retry
Read Count: 3
Read Count: 3
incremented: 4
Read Count: 4
incremented: 5