Lock implementations guards a shared resource allowing a single thread to operate at a time. Lock implementations provide more extensive locking operations than can be obtained using synchronized methods and statements.

General behavior is as follows:

A Lock is initialized to guard a shared resource. The lock method acquires the lock and blocks other threads. When owner of the lock finishes its work, it releases the lock with unlock method. Generally, operation is done in a try-finally clause, so unlock is always called and the lock is released.

Lets say you have a shared counter that is being used by multiple threads. These threads will increment the counter, but you have following requirements:

  • Shared resource should be guarded with lock, so counter data is reliable.

To achieve this we will follow 2 different options. In the first option threads will wait for the lock and in the second option they will try to acquire lock but will not wait.

We will use Lock interface and its implementation ReentrantLock. Shared resource (in this case counter) will maintain a ReentrantLock and will manage parallel execution.

 

public class LockTest {

    public static void main(String[] args) throws InterruptedException {
        lock();
        tryLock();
    }

    public static void lock() throws InterruptedException {
        final SharedCounter sharedCounter = new SharedCounter();
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        class Caller implements Runnable {

            @Override
            public void run() {
                System.out.printf("Result: %s%n", sharedCounter.increment());
            }
        }

        threadPool.execute(new Caller());
        threadPool.execute(new Caller());
        threadPool.execute(new Caller());
        threadPool.shutdown();
        threadPool.awaitTermination(10, TimeUnit.SECONDS);
    }

    public static void tryLock() throws InterruptedException {
        final SharedCounter sharedCounter = new SharedCounter();
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        class Caller implements Runnable {

            @Override
            public void run() {
                System.out.printf("Result: %s%n", sharedCounter.maybeIncrement());
            }
        }

        threadPool.execute(new Caller());
        threadPool.execute(new Caller());
        threadPool.execute(new Caller());
        threadPool.shutdown();
        threadPool.awaitTermination(10, TimeUnit.SECONDS);
    }

    private static class SharedCounter {

        private int value = 0;
        private Lock lock = new ReentrantLock();

        public int increment() {
            System.out.printf("%s is attempting to lock%n", Thread.currentThread().getName());
            lock.lock();
            try {
                System.out.printf("%s has acquired lock%n", Thread.currentThread().getName());
                sleep(100);
                return ++value;
            } finally {
                System.out.printf("%s is unlocking%n", Thread.currentThread().getName());
                lock.unlock();
            }
        }

        public int maybeIncrement() {
            System.out.printf("%s is attempting to lock%n", Thread.currentThread().getName());
            if (lock.tryLock()) {
                try {
                    System.out.printf("%s has acquired lock%n", Thread.currentThread().getName());
                    sleep(100);
                    return ++value;
                } finally {
                    System.out.printf("%s is unlocking%n", Thread.currentThread().getName());
                    lock.unlock();
                }
            } else {
                System.out.printf("%s could not acquire lock, passing%n", Thread.currentThread().getName());
                return value;
            }
        }
    }
}

 

Leave a Reply

Close Menu