ZOFTINO.COM android and web dev tutorials

Java Concurrency Lock and Condition Examples

Java concurrency framework provides external Locks which are similar to intrinsic lock obtained entering synchronized blocks but give flexibility and provide other features. In this tutorial, you can learn Lock and ReadWriteLock interfaces, ReentrantLock and ReentrantReadWriteLock lock implementations and Conditions with examples.

Table of Contents

Locks

When a thread enters synchronized block of statements or synchronized methods, it obtains the lock of interested object in which there is a shared data between threads to avoid data corruption. Locks allow only one thread at time to access the share resources. But the intrinsic lock obtained this way has limited operations. It is not flexible as it can only be acquired when it enters a block and must be released when it exist the block. If a thread acquires multiple locks, they must be releases in the opposite order meaning the last acquired lock is the first one to be released, then second latest one, and so on.

Java concurrency framework Locks provide extensive locking operations and allow flexibility. Lock implementations provided in concurrency framework allow acquiring and releasing locks at any scope, meaning for releasing locks, order doesn’t have to be followed like intrinsic lock. This feature is called hand over hand or chain locking, with the feature, a thread can acquire lock A and lock B, then release lock A, then acquired lock C and so on.

With this external locks, a thread can try to acquire a lock for certain duration of time then back out if it can’t acquire lock in the specified time duration or a thread can immediately back out if a lock is not available. And also, when a thread is waiting for or trying to acquire lock, if the thread is interrupted, the operation will not block the thread.

Like intrinsic lock, Lock object can be owned by one thread at a time and inter thread communication can be implemented using wait and notify mechanism. Condition object provide means for a thread to wait for a condition to change and be notified by other threads.

Lock Interface

Lock interface in Java concurrency framework defines methods for acquiring locks in different forms. Method lock() is similar to intrinsic lock, can block the current thread if lock can’t be acquired and can cause deadlock.

Method lockInterruptibly() allows the current thread to acquire the lock if it is available and the thread is not interrupted. If lock is not available and thread is not interrupted, it will wait till the lock is available.

Method tryLock() allows the current uninterrupted thread to acquire the lock if available. Otherwise, it will exit or retry till the specified time expires depending on the version of tryLock() method.

Method unlock() releases the lock. It is a best practice to keep the block of statements, which execute after acquiring the lock, in try block and call unlock method on the lock object in the finally block to release the lock.

ReentrantLock Example

ReentrantLock implements Lock interface and it is similar to intrinsic lock. Current thread obtains lock by calling lock method on it. If lock is not available, thread will block waiting for lock. When multiple threads wait for lock, ReentrantLock can be made to give priority using fair policy mechanism to the longest waiting thread by giving lock it. ReentrantLock with fairness policy can be created by passing true value to its constructor.

In addition to implementing methods defined in the Lock interface, ReentrantLock provides methods such as getHoldCount(), getOwner(), getQueuedThreads(), isFiar(), isHeldByCurrentThread() and newConditions().

Following example shows how to use ReentrantLock. The example instantiates ReentrantLock object and calls lock method on it in both threads. First thread obtains lock and executes task while second thread waits for the lock to be released. Second thread obtains the lock after first thread is done with execution. Method unlock is called in the finally block to release the lock. To check whether lock is obtained by the current thread, you can use isHeldByCurrentThread() method.

public static void main(String[] args) {

	ReentrantLock rlock =  new ReentrantLock(true);

	Thread threadOne = new Thread(new Runnable() {
		@Override
		public void run() {
			rlock.lock();
			try {
				if(rlock.isHeldByCurrentThread()) {
					System.out.println("thread one lock obtained");
					Thread.sleep(5000);
				}

			} catch (InterruptedException e) {	}
			finally {
				if(rlock.isHeldByCurrentThread()) {
					rlock.unlock();
				}
			}
		}			
	});		
	Thread threadTwo = new Thread(new Runnable() {
		@Override
		public void run() {
			rlock.lock();
			try {
				if(rlock.isHeldByCurrentThread()) {
					System.out.println("thread two lock obtained");
				}
			}finally {
				if(rlock.isHeldByCurrentThread()) {
					rlock.unlock();
				}
			}
		}			
	});		
	threadOne.start();
	threadTwo.start();
	System.out.println("main thread");
}

Output

thread one lock obtained
main thread
thread two lock obtained

If you call tryLock() method to obtain lock in both threads, second thread will not block and continue execution. Here is the output of the above program with tryLock() instead of lock()

thread one lock obtained
main thread

You can make a thread to try to lock for certain amount of time.

	try {
		rlock.tryLock(5500, TimeUnit.MILLISECONDS);
	} catch (InterruptedException e) {

	}

If you add above code to second thread in our example, you will get output similar to first use case. The only difference is that if second thread is interrupted by calling interrupt() method on it, tryLock will throw interrupted exception and will not block the thread waiting the lock.

You can get a lock using lockInterruptibly() method also, the difference between lock and lockInterruptibly methods is that lockInterruptibly method doesn’t block or provide lock if the current thread is interrupted.

ReadWriteLock

Threads can obtain read lock and write lock using ReadWriteLock. Read lock is for read only operations and write lock is for writes. Thread which acquires read lock can see updates made by the thread which previously released the write lock.

Though ReadWriteLock has read and write lock, only one lock, either read lock or write is used at a given point of time. But multiple threads can own read locks at the same time as long as there are no writers. ReadWriteLock improves the performance where there are a lot of reads and few writes as multiple threads can simultaneously own read lock.

ReadWriteLock interface defines two methods for threads to obtain read and write locks, readLock() and writeLock() respectively. ReentrantReadWriteLock is an implementation of ReadWriteLock interface.

ReentrantReadWriteLock Example

Following example shows how to use read and write locks using ReentrantReadWriteLock.

public class ShoppingCart {
	private List<String> products = new ArrayList<String>();
	
	public String getProduct(int i) {
		return products.get(i);
	}
	
	public void addProduct(String name) {
		products.add(name);
	}
}
public static void main(String[] args) {

	//instantiate share object
	ShoppingCart scart = new ShoppingCart();
	scart.addProduct("iphone");
	
	//instantiate ReentrantReadWriteLock
	ReentrantReadWriteLock rrwlock = new ReentrantReadWriteLock();

	Thread threadRead = new Thread(new Runnable() {
		@Override
		public void run() {
			ReadLock rl = rrwlock.readLock();
			try {
				//obtain read lock, so that no write occur during reading
				rl.lock();
				//read from shared object
				System.out.println(scart.getProduct(0));
				Thread.sleep(1000);
			} catch (InterruptedException e) {	}
			finally {
				//release read lock
				rl.unlock();
			}
		}			
	});		
	Thread threadWrite = new Thread(new Runnable() {
		@Override
		public void run() {
			//get write lock
			//lock() block current thread till it acquires write lock
			//if you don't want to block current thread, use tryLock()
			rrwlock.writeLock().lock();
			try {				
				//if tryLock() is used, check if write lock is obtained
				if(rrwlock.isWriteLockedByCurrentThread()) {
					//write to shared object
					scart.addProduct("pixel");
					System.out.println("thread write lock obtained");
					Thread.sleep(1000);
				}
			}catch (InterruptedException e) {	}
			finally {
				if(rrwlock.isWriteLockedByCurrentThread()) {
					//release or unlock write lock
					rrwlock.writeLock().unlock();
				}
			}
		}			
	});	
	
	threadRead.start();
	threadWrite.start();		
	
	System.out.println("main thread");
}

ReentrantReadWriteLock can be used in fair mode by passing true value to its constructor. When used in fair mode, ReentrantReadWriteLock assigns write lock to the longest waiting thread. If group of threads waiting for read lock, then it will assign the read lock to the group. When used in non-fair mode, order of granting locks is not maintained.

ReentrantReadWriteLock rrwlock = new ReentrantReadWriteLock(true);

ReentrantReadWriteLock supports reentrancy meaning a reader which acquired read lock can reacquire read lock. Similarly a writer which acquired write lock can reacquire write lock. And also, a writer which acquired write lock can acquire read lock, but reverse is not allowed meaning write to read downgrade is allowed and read to write lock upgrade is not possible. Reentrancy is useful in the scenario where a writer calls methods which use read lock.

	rrwlock.writeLock().lock();
	rrwlock.readLock().lock();
	try {				
		//do something
	}finally {
		rrwlock.readLock().unlock();
		rrwlock.writeLock().unlock();
	}
	rrwlock.writeLock().lock();
	rrwlock.writeLock().lock();
	try {				
		//do something
	}finally {
		rrwlock.writeLock().unlock();
		rrwlock.writeLock().unlock();
	}

Condition

Like threads communicate using wait(), notify() and notifyAll() methods of object when intrinsic lock is used to restrict access to a shared objects, Condition allows inter threads communication when external locks are used. Condition defines mechanism to suspend a thread until another thread notifies it. Using condition, deadlock issue can be avoided with external locks.

Condition object is obtained by calling newCondition() methods on lock object, ReentrantLock or ReentrantReadWriteLock. Condition defines methods such as await(), signal() and signalAll() for waiting and notifying. Overloaded await() methods allows you to specify the duration of the wait. Method siganlAll() notifies all waiting threads.

Condition Example

Following example shows how to use condition object for communication between two threads when ReentrantLock is used to restrict access to share message object which contains methods to publish and view messages. If the last message is not consumed, publish-message thread waits using a condition object and calling awaits() on it. Similarly, if there is no new message, view-message thread waits using another condition object and calling awaits method on it.

When publish-message thread sets new message, it will notify view-message thread by calling signal() on the condition object on which view-message thread is waiting. Similarly, when view-message thread consumes the message, it will notify publish-message thread by calling signal() method on the condition object on which publish-message thread is waiting.

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Message {

	final private Lock lock = new ReentrantLock();
	final private Condition producedMsg  = lock.newCondition(); 
	final private Condition consumedMsg = lock.newCondition(); 

	private String message;
	private boolean messageState;
	private boolean endIt;

	public void viewMessage() {
		//lock
		lock.lock();
		try {
			//no new message wait for new message
			while (!messageState) 
				producedMsg.await();

			System.out.println("Here is the latest message : "+message);
			messageState = false;
			//message consumed, notify waiting thread
			consumedMsg.signal();

		}catch(InterruptedException ie) {
			System.out.println("Thread interrupted - viewMessage");
		}finally {
			lock.unlock();
		}
	}
	public void publishMessage(String message) {
		lock.lock();
		try {
			//last message not consumed, wait for it be consumed
			while (messageState) 
				consumedMsg.await();

			System.out.println("adding latest message ");
			this.message = message;
			messageState = true;
			//new message added, notify waiting thread
			producedMsg.signal();

		}catch(InterruptedException ie) {
			System.out.println("Thread interrupted - publishMessage");
		}finally {
			lock.unlock();
		}

	}
	
	public boolean isEndIt() {
		return endIt;
	}
	public void setEndIt(boolean endIt) {
		this.endIt = endIt;
	}
}

Message producer.

public class MessageProducer implements Runnable {
	private Message message;

	public MessageProducer(Message msg) {
		message = msg;
	}
	
	@Override
	public void run() {
		pusblishMessages();
	}
	
	private void pusblishMessages(){
		List<String> msgs = new ArrayList<String>();
		msgs.add("hello");
		msgs.add("current project is complete");
		msgs.add("here is the estimation for new project");		
		
		for(String msg :  msgs) {
			message.publishMessage(msg);
			try {
	            Thread.sleep(400);
	        } catch (InterruptedException e) {}
		}
		
		message.publishMessage("bye");
		message.setEndIt(true);
	}
}

Message viewer.

public class MessageViewer implements Runnable{
	private Message message;

	public MessageViewer(Message msg) {
		message = msg;
	}

	@Override
	public void run() {
		while(!message.isEndIt())
			message.viewMessage();
	}
}

Start two threads.

public static void main(String[] args) {
	
	Message msg = new Message();
	Thread messageProducer = new Thread(new MessageProducer(msg));
	Thread messageViewer = new Thread(new MessageViewer(msg));
	messageProducer.start();
	messageViewer.start();

}