Java Lock and Condition Example using Producer Consumer Solution

You can also solve the producer-consumer problem by using a new lock interface and condition variable instead of using the synchronized keyword and wait and notify methods.  The lock provides an alternate way to achieve mutual exclusion and synchronization in Java. Advantage of Lock over synchronized keyword is well known, explicit locking is much more granular and powerful than synchronized keyword, for example, the scope of lock can range from one method to another but the scope of synchronized keyword cannot go beyond one method. Condition variables are instance of java.util.concurrent.locks.Condition class, which provides inter-thread communication methods similar to wait, notify and notifyAll e.g. await(), signal(), and signalAll().

So if one thread is waiting on a condition by calling condition.await() then once that condition changes, the second thread can call condition.signal() or condition.signalAll() method to notify that its time to wake-up, the condition has been changed.

Though Lock and Condition variables are powerful they are slightly difficult to use for first-timers. If you are used to locking using a synchronized keyword, you will use Lock painful because now it becomes the developer's responsibility to acquire and release the lock. Anyway, you can follow code idiom shown here to use Lock to avoid any concurrency issue.

In this article, you will learn how to use Lock and Condition variables in Java by solving the classic Producer-Consumer problem. In order to deeply understand these new concurrency concepts.

And, if you are serious about mastering Java multi-threading and concurrency then I also suggest you take a look at the Java Multithreading, Concurrency, and Performance Optimization course by Michael Pogrebinsy on Udemy. It's an advanced course to become an expert in Multithreading, concurrency, and Parallel programming in Java with a strong emphasis on high performance

How to use Lock and Condition variable in Java

You need to be little bit careful when you are using Lock class in Java. Unlike synchronized keyword, which acquire and release lock automatically, here you need to call lock() method to acquire the lock and unlock() method to release the lock, failing to do will result in deadlock, livelock or any other multi-threading issues.  In order to make developer's life easier, Java designers has suggested following idiom to work with locks :

 Lock theLock = ...;
     try {
         // access the resource protected by this lock
     } finally {

Though this idiom looks very easy, Java developer often makes subtle mistakes while coding e.g. they forget to release lock inside finally block. So just remember to release lock in finally to ensure lock is released even if try block throws any exception.

How to create Lock and Condition in Java?

Since Lock is an interface, you cannot create object of Lock class, but don't worry, Java provides two implementation of Lock interface, ReentrantLock and ReentrantReadWriteLock. You can create object of any of this class to use Lock in Java. BTW, the way these two locks are used is different because ReentrantReadWriteLock contains two locks, read lock and write lock. In this example, you will learn how to use ReentrantLock class in Java. One you have an object, you can call lock() method to acquire lock and unlock() method to release lock. Make sure you follow the idiom shown above to release lock inside finally clause.

In order to wait on explicit lock, you need to create a condition variable, an instance of java.util.concurrent.locks.Condition class. You can create condition variable by calling lock.newCondtion() method. This class provides method to wait on a condition and notify waiting threads, much like Object class' wait and notify method. Here instead of using wait() to wait on a condition, you call await() method. Similarly in order to notify waiting thread on a condition, instead of calling notify() and notifyAll(), you should use signal() and signalAll() methods. Its better practice to use signalAll() to notify all threads which are waiting on some condition, similar to using notifyAll() instead of notify().

Just like multiple wait method, you also have three version of await() method, first await() which causes current thread to wait until signalled or interrupted,  awaitNanos(long timeout) which wait until notification or timeout and awaitUnInterruptibly() which causes current thread to wait until signalled. You can not interrupt the thread if its waiting using this method. Here is sample code idiom to use Lock interface in Java :

How to use Lock and Condition variables in Java

Producer Consumer Solution using Lock and Condition

Here is our Java solution to classic Producer and Consumer problem, this time we have used Lock and Condition variable to solve this. If you remember in past, I have shared tutorial to solve producer consumer problem using wait() and notify() and by using new concurrent queue class BlockingQueue. In terms of difficultly, first one using wait and notify is the most difficult to get it right and BlockingQueue seems to be far easier compared to that. This solution which take advantage of Java 5 Lock interface and Condition variable sits right in between these two solutions.

Explanation of Solution
In this program we have four classes, ProducerConsumerSolutionUsingLock is just a wrapper class to test our solution. This class creates object of ProducerConsumerImpl and producer and consumer threads, which are other two classes extends to Thread acts as producer and consumer in this solution.

import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

 * Java Program to demonstrate how to use Lock and Condition variable in Java by
 * solving Producer consumer problem. Locks are more flexible way to provide
 * mutual exclusion and synchronization in Java, a powerful alternative of
 * synchronized keyword.
 * @author Javin Paul
public class ProducerConsumerSolutionUsingLock {

    public static void main(String[] args) {

        // Object on which producer and consumer thread will operate
        ProducerConsumerImpl sharedObject = new ProducerConsumerImpl();

        // creating producer and consumer threads
        Producer p = new Producer(sharedObject);
        Consumer c = new Consumer(sharedObject);

        // starting producer and consumer threads


class ProducerConsumerImpl {
    // producer consumer problem data
    private static final int CAPACITY = 10;
    private final Queue queue = new LinkedList<>();
    private final Random theRandom = new Random();

    // lock and condition variables
    private final Lock aLock = new ReentrantLock();
    private final Condition bufferNotFull = aLock.newCondition();
    private final Condition bufferNotEmpty = aLock.newCondition();

    public void put() throws InterruptedException {
        try {
            while (queue.size() == CAPACITY) {
                        + " : Buffer is full, waiting");

            int number = theRandom.nextInt();
            boolean isAdded = queue.offer(number);
            if (isAdded) {
                System.out.printf("%s added %d into queue %n", Thread
                        .currentThread().getName(), number);

                // signal consumer thread that, buffer has element now
                        + " : Signalling that buffer is no more empty now");
        } finally {

    public void get() throws InterruptedException {
        try {
            while (queue.size() == 0) {
                        + " : Buffer is empty, waiting");

            Integer value = queue.poll();
            if (value != null) {
                System.out.printf("%s consumed %d from queue %n", Thread
                        .currentThread().getName(), value);

                // signal producer thread that, buffer may be empty now
                        + " : Signalling that buffer may be empty now");

        } finally {

class Producer extends Thread {
    ProducerConsumerImpl pc;

    public Producer(ProducerConsumerImpl sharedObject) {
        this.pc = sharedObject;

    public void run() {
        try {
        } catch (InterruptedException e) {

class Consumer extends Thread {
    ProducerConsumerImpl pc;

    public Consumer(ProducerConsumerImpl sharedObject) {
        this.pc = sharedObject;

    public void run() {
        try {
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block

CONSUMER : Buffer is empty, waiting
PRODUCER added 279133501 into queue 
PRODUCER : Signalling that buffer is no more empty now
CONSUMER consumed 279133501 from queue 
CONSUMER : Signalling that buffer may be empty now

Here you can see that CONSUMER thread has started before PRODUCER thread and found that buffer is empty, so its waiting on condition "bufferNotFull". Later when PRODUCER thread started, it added an element into shared queue and signal all threads (here just one CONSUMER thread) waiting on condition bufferNotFull that this condition may not hold now, wake up and do your work. Following call to signalAll() our CONSUMER thread wake up and checks the condition again, found that shred queue indeed no more empty now, so it has gone ahead and consumed that element from the queue.

Since we are not using any infinite loop in our program, post this action CONSUMER thread came out of run() method, and thread is finished. PRODUCER thread is already finished, so our program ends here.

That's all about how to solve the producer-consumer problem using lock and condition variable in Java. It's a good example to learn how to use these relatively less utilized but powerful tools. Let me know if you have any question about lock interface or condition variables, happy to answer. If you like this Java concurrency tutorial and want to learn about other concurrency utilities, You can take a look at the following tutorials as well.

Java Concurrency Tutorials for Beginners

  • How to use Future and FutureTask class in Java? (solution)
  • How to use a CyclicBarrier class in Java? (example)
  • How to use Callable and Future class in Java? (example)
  • How to use CountDownLatch utility in Java? (example)
  • How to use Semaphore class in Java? (code sample)
  • What is difference between CyclicBarrier and CountDownLatch in Java? (answer)
  • How to use Lock interface in multi-threaded programming? (code sample)
  • How to use Thread pool Executor in Java? (example)
  • 10 Multi-threading and Concurrency Best Practices for Java Programmers (best practices)
  • 50 Java Thread Questions for Senior and Experienced Programmers (questions)
  • Top 5 Concurrent Collection classes from Java 5 and Java 6 (read here)
  • how to do inter thread communication using wait and notify? (solution)
  • How to use ThreadLocal variables in Java? (example)

Further Learning
Multithreading and Parallel Computing in Java
Java Concurrency in Practice - The Book
Applying Concurrency and Multi-threading to Common Java Patterns
Java Concurrency in Practice Course by Heinz Kabutz


scanty said...

Thanks for posting a nice explanation of producer consumer problem in java.
I have a question regarding use of notifyAll() method here.
As we are using two conditional variables for queue full and empty, can't we just use notify() to notify either one consumer or producer(depending upon the conditional variable) ?
This will save the overhead of waking up multiple threads, even though only one can proceed.

javin paul said...

@scanty, you are correct in terms of more threads getting notification but that's done purposefully to avoid risk of notification being lost. I would suggest to read difference between notify and notifyAll in Java to learn more.

scanty said...

Thanks for the reply.
Consider this scenario with the buffer being empty and the producer trying to insert an element in buffer.
To send a notification the producer thread must take a lock, which will only be released when the consumer releases the lock temporarily while waiting for the buffer to be non-empty.
So, when producer gets the lock and adds the element it sends notification, which is received by the consumer.
Am I missing something here leading to the notification being lost :)

If there is only one producer and consumer, how will notifyAll help over notify if there is a chance for the notification to get lost with either of the threads?

Unknown said...

are the variables buffernotfull and buffer not empty switched here? why will producer sings, buffer not full after adding element. it should signal buffer not empty right?

Unknown said...

ReentrantReadWriteLock is not an implementation of Lock interface.It is a implementation of ReadWriteLock Interface

Raghavendra T.A said...

Thank you. I was searching for this example for hours.

Anonymous said...

I have a question as to why two condition variables are used? I have coded this using a single condition variable and it works fine. (This is essentially the way synchronized/wait/notifyAll works.) Are two condition variables used for clarity or some other reason?

Bob E.

Post a Comment