Java explicit lock example
Introduction
Before Java 5 - where the java.util.concurrent package concurrency facilities were introduced - we had to implement locking mechanisms from scratch. Even now when Java already offers a robust locking infra-structure one could need some very specific locking strategy that may be only possible if implemented from scratch.
In this tutorial we will implement a lock in Java resorting to the synchronized statement together with the wait/notify idiom. The locking mechanism we are going to implement is also feasible using the locking infra-structure that was included in Java 5, so it will be mostly for educational purposes and as an example to other explicit locking mechanisms you may require.
For more information on the synchronized modifier please refer to Java synchronized example.
The lock usage
We are going to implement a lock that may be used to synchronize access to some shared resource like a database. It could be used to access any other sensitive component or section of your application.
The lock will provide a couple of methods:
public boolean lock(int timeout){ // Try to acquire the lock. // If timeout is reached before // acquiring the lock we should give up } public void releaseLock() { // Release the previously acquired lock }
Using the methods described above, an example usage of the lock could look like the following:
Lock lock = LockProvider.getDatabaseLock(); boolean lockAcquired = lock.lock(2000); if (lockAcquired) { try { doWorkWithTheDatabase(); } finally { lock.releaseLock(); } } else { throw new RuntimeException("Could not acquire lock!"); }
Method doWorkWithTheDatabase will be executed by a single thread at a given time after the thread acquires the lock.
The lock implementation
Following next is a possible lock implementation (actually it is a reentrant lock i.e. a thread may acquire the same lock for an arbitrary number of times but then it must release it for the same number of times it previously acquired it):
package com.byteslounge.concurrency; import java.util.concurrent.TimeUnit; public class Lock { private final Object lockObject = new Object(); private Thread lockingThread; private int lockCount = 0; public boolean lock(int timeout) { synchronized (lockObject) { if (lockingThread != null && lockingThread != Thread.currentThread()) { try { long elapsedTime = 0L; long startWaitingTime; while (lockingThread != null) { startWaitingTime = TimeUnit.NANOSECONDS.toMillis(System .nanoTime()); lockObject.wait(timeout); elapsedTime += TimeUnit.NANOSECONDS.toMillis(System .nanoTime()) - startWaitingTime; if (timeout > 0 && elapsedTime > timeout) { return false; } } } catch (InterruptedException e) { return false; } } lockCount++; if (lockingThread == null) { lockingThread = Thread.currentThread(); } return true; } } public void releaseLock() { synchronized (lockObject) { if (lockingThread != Thread.currentThread()) { throw new IllegalStateException( "Only lock holding thread may release the lock!"); } lockCount--; if (lockCount == 0) { lockingThread = null; lockObject.notify(); } } } }
When we try to acquire the lock through lock method we check if there is no other thread already holding the lock (lockingThread != null) and if the lock holding thread is not already the current thread (lockingThread != Thread.currentThread()).
It the holding thread is already the current thread we increment the lock counter. We are implementing a reentrant lock i.e. a thread may acquire the same lock an arbitrary number of times. In order to release the lock the thread must release it the same number of times it previously acquired it.
When the lock is already acquired by another thread we enter the waiting loop. Threads will hold execution in the wait statement until the waiting timeout expires or in case of a notification from releaseLock method occurs.
For each waiting condition loop iteration we check if the timeout has already expired. If the timeout did expire we give up of waiting for the lock.
Testing
Let's do some testing of our lock implementation:
package com.byteslounge.concurrency; public class Test { public static void main(String[] args) throws InterruptedException { Lock lock = new Lock(); MyThread[] threadArray = new MyThread[10]; for (int i = 0; i < 10; i++) { threadArray[i] = new MyThread(lock); } for (int i = 0; i < 10; i++) { threadArray[i].start(); } for (int i = 0; i < 10; i++) { threadArray[i].join(); } } static class MyThread extends Thread { private final Lock lock; public MyThread(Lock lock) { this.lock = lock; } @Override public void run() { boolean lockAcquired = lock.lock(2000); if (lockAcquired) { System.out .println("Thread " + this.getId() + ": Lock acquired"); try { System.out.println("Thread " + this.getId() + ": Working"); } finally { lock.releaseLock(); System.out.println("Thread " + this.getId() + ": Lock released"); } } else { throw new RuntimeException("Could not acquire lock!"); } } } }
The following output is the result of a single execution of our test class:
Thread 8: Working
Thread 8: Lock released
Thread 9: Lock acquired
Thread 9: Working
Thread 9: Lock released
Thread 10: Lock acquired
Thread 10: Working
Thread 10: Lock released
Thread 11: Lock acquired
Thread 11: Working
Thread 11: Lock released
Thread 12: Lock acquired
Thread 12: Working
Thread 12: Lock released
Thread 13: Lock acquired
Thread 13: Working
Thread 13: Lock released
Thread 14: Lock acquired
Thread 14: Working
Thread 14: Lock released
Thread 17: Lock acquired
Thread 17: Working
Thread 17: Lock released
Thread 15: Lock acquired
Thread 15: Working
Thread 15: Lock released
Thread 16: Lock acquired
Thread 16: Working
Thread 16: Lock released
The lock implementation source code is available for download at the end of this page.