Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
What is the Condition
For any Java Object, it has a set of monitor methods defined on java.lang.object, including wait(), wait(long timeout), notify(), notifyAll(), These methods are used in conjunction with the synchronized keyword to implement the wait/notify mode. Similarly, the Condition interface provides an Object-like approach to implement the wait/notification mode in conjunction with Lock. Take a look at the Object monitor method compared to the Condition interface:
Condition addresses producer-consumer issues
It is assumed that the producer can produce tickets, but only one of the existing tickets can be produced until the customer buys them. Condition can therefore be used to ensure synchronization. Havenum says there are tickets and producers need to wait; Nonum says there are no tickets and consumers have to wait. The code is as follows:
class tickets{
private int num = 0;
ReentrantLock lock = new ReentrantLock();
Condition nonum = lock.newCondition();
Condition havenum = lock.newCondition();
public void put(a) throws InterruptedException {
lock.lock();
try{
while(num==1)
{
nonum.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"One copy was produced and the current quantity is."+num);
havenum.signalAll();
}
finally{ lock.unlock(); }}public void take(a) throws InterruptedException {
lock.lock();
try {
while(num==0)
{
havenum.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"Consumed one serving. The current quantity is."+num);
nonum.signalAll();
}
finally{ lock.unlock(); }}}Copy the code
Condition accurate awakening
The Condition mechanism can be used to wake up in order. The Condition mechanism can be used to wake up in order. The Condition mechanism can be used to wake up in order. We can also see from the above example that different Condition objects can be used to wake up different threads. This mechanism can be used to achieve accurate wake up.
class Aweaken
{
ReentrantLock lock = new ReentrantLock();
int num = 1;
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
public void weakA(a)
{
lock.lock();
try {
while(num ! =1)
{
conditionA.await();
}
num = 2;
System.out.println("Now it's a thread."+Thread.currentThread().getName()+"Thread B should be next.");
conditionB.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally{ lock.unlock(); }}public void weakB(a)
{
lock.lock();
try {
while(num ! =2)
{
conditionB.await();
}
num = 3;
System.out.println("Now it's a thread."+Thread.currentThread().getName()+"Thread C should be next.");
conditionC.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally{ lock.unlock(); }}public void weakC(a)
{
lock.lock();
try {
while(num ! =3)
{
conditionC.await();
}
num = 1;
System.out.println("Now it's a thread."+Thread.currentThread().getName()+"Thread A should be next.");
conditionA.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally{ lock.unlock(); }}}public class PCold {
public static void main(String[] args) {
Aweaken b = new Aweaken();
new Thread(()->{
for (int i = 0; i < 10; i++) { b.weakA(); }},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) { b.weakB(); }},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) { b.weakC(); }},"C").start(); }}Copy the code
Condition implementation analysis
Waiting queue
Condition.await() condition.await () condition.await () condition.await () condition.await () The thread releases the lock and the node joins the wait queue and enters the wait state.
As you can see from the figure below, the Condition has a reference to the first and last nodes, while the new node simply points the original nextWaiter to it and updates the last node. The above node-reference update process does not use CAS because the thread calling await() must be the one that has acquired the lock, which keeps the thread safe.
A Lock (synchronizer) has a synchronization queue and an overdrink wait queue (as shown below)
Waiting for the
Calling the await() method of Condition causes the current thread to enter the wait queue and release the lock while the thread state changes to wait. When returning from await(), the current thread must have acquired the lock associated with Condition.
The thread firing await() can be seen as moving the first node of the synchronization queue (the current thread must have successfully acquired the lock and therefore must be in the first node of the synchronization queue) to the tail node of the Condition’s wait queue, releasing the synchronization state into the wait state and waking up the subsequent nodes of the synchronization queue.
Wake up the
Calling the signal() method of Condition wakes up the first node in the rewait queue, which is by far the longest waiting node. Call the signalAll() method of Condition to wake up all the nodes in the wait queue, which is equivalent to executing signal() once for each node in the wait queue.