There are a few strange things about Condition related code in Java concurrency. I’ll use the following code to show you.
The code is about one cup and two threads. One thread is trying to fill it, the other to drink from it.
The code uses explicit Lock + Condition
API. But what I’m going to explain also applies to implicit lock + object monitor (i.e. synchronized and wait()/notify()
)
package concurrency.producer_consumer; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Cup { private volatile boolean filled = false; private Lock lock = new ReentrantLock(); private Condition sharedCondition = lock.newCondition(); //"shared" because both drinker and filler are using it public void drink() throws InterruptedException { try { lock.lock(); //l1 while (!filled) { //lp1 sharedCondition.await(); //w1 } System.out.println(Thread.currentThread().getName() + " just drank from the cup"); //a1 filled = false; sharedCondition.signal(); //s1 } finally { lock.unlock(); //ul1 } } public void fill() throws InterruptedException { try { lock.lock(); while (filled) { sharedCondition.await(); } System.out.println(Thread.currentThread().getName() + " just filled the cup"); filled = true; sharedCondition.signal(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { Cup cup = new Cup(); Thread filler = new Thread(() -> { for (int i = 0; i < 10; i++) { try { cup.fill(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "filler"); Thread drinker = new Thread(() -> { for (int i = 0; i < 10; i++) { try { cup.drink(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "drinker"); drinker.start(); filler.start(); drinker.join(); filler.join(); } }
There are a few things that are not intuitive:
- The
sharedCondition
is called a condition, but it’s not a predicate. It’s just a dummy object which itself doesn’t carry any logic. - Instead, you need to use your own state variable to mean the real condition. Here it’s
filled
- You may think line
l1
andul1
together form a pair for locking/unlocking, and defines a critical section. But not really.- If there is no need to wait, then yes, they are a pair
- Otherwise
- Line
l1
and some unlocking insidew1
form a pair.sharedCondition.await()
releases the lock. - some locking inside line
w1
and lineul1
form a pair. When a thread exitssharedCondition.await()
, it will acquire the lock
- Line
- Where does the program go after going into
sharedCondition.await()
? Is the method calling over? And what happened after being woken up?- When a thread goes into line
w1
, the thread sleeps, but the method call ofdrink()
is not over, as the call ofsharedCondition.await()
is not over - After being woken up,
then finishes. And then the thread moves up to linesharedCondition.await()
lp1
, to check the state again. If now the state is satisfying, it moves down to linea1
- When a thread goes into line
One more thing to help understanding the code is that there is a queue of sleeping threads, related to each condition.
- By
condition.await()
, current thread puts itself into a queue until some biz state is reached - By
condition.signal()
, current thread wakes up another thread in the queue
Hope you can now use Lock and Condition more smoothly.