Book version: November 2015, 1st edition, 3rd printing
Problem overview:
3.1.10 Mainly explain the scene of waiting for the wait condition to change
For the sake of convenience, I will not copy the original code in the book. I use my own code is a main method I use jdk1.6
public static void main(String[] args) throws InterruptedException { final List<String> list = new ArrayList<String>(); final Object lock = new Object(); // wait & delete Runnable waitRun = new Runnable() { @Override public void run() { synchronized( lock ){ try { if( list.size() == 0 ){ System.out.println("wait begin t="+Thread.currentThread().getName()); lock.wait(); System.out.println("wait end"+Thread.currentThread().getName()); } System.out.println("list remove begin"+Thread.currentThread().getName()); list.remove( 0 ); System.out.println("list remove end "+Thread.currentThread().getName() + " size="+list.size()); } catch (InterruptedException e) { e.printStackTrace (); } } } }; // wake up & increase Runnable notifyRun = new Runnable() { @Override public void run() { synchronized( lock ){ System.out.println("list add"); list.add( "1" ); System.out.println("notify begin"); lock.notifyAll(); System.out.println("notify end"); } } }; //Thread 1 - delete operation lock waiting Thread waitT1 = new Thread( waitRun ); waitT1.start(); //Thread 2 - delete operation lock waiting Thread waitT2 = new Thread( waitRun ); waitT2.start(); Thread.sleep( 1000L ); //Thread 3 - Increment operation wakes up all waiting Thread notifyT1 = new Thread( notifyRun ); notifyT1.start(); }
There is a problem in this way, as in the book it is said that there will be a delete operation exception because there are no elements to delete.
Exception in thread "Thread-0" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
The solution given in the book is this
is to delete the thread while(list.size==0) This ensures that there are element to perform the delete operation.
// wait & delete Runnable waitRun = new Runnable() { @Override public void run() { synchronized( lock ){ try { // Here is the modification point changed from the original if to while while( list.size() == 0 ){ System.out.println("wait begin t="+Thread.currentThread().getName()); lock.wait(); System.out.println("wait end"+Thread.currentThread().getName()); } System.out.println("list remove begin"+Thread.currentThread().getName()); list.remove( 0 ); System.out.println("list remove end "+Thread.currentThread().getName() + " size="+list.size()); } catch (InterruptedException e) { e.printStackTrace (); } } } };
This does ensure that the element is deleted only when there is an element, because when you have no element, the wait() operation will be looped all the time.
But there is a problem with the second deletion thread, because the collection has no elements to delete, there will be one more wait, and this wait() is generated in notifyAll and will never wake up.
So this approach belongs to solving old problems and creating new ones.
And my approach is this
Runnable waitRun = new Runnable() { @Override public void run() { synchronized( lock ){ try { System.out.println("wait begin t="+Thread.currentThread().getName()); lock.wait(); System.out.println("wait end"+Thread.currentThread().getName()); // Whether the delete has been performed boolean deleteFlag = false; System.out.println( "list size="+list.size() +" remove before"); while( list.size() > 0 && deleteFlag == false ){ System.out.println("list remove begin"+Thread.currentThread().getName()); list.remove( 0 ); System.out.println("list remove end "+Thread.currentThread().getName() + " size="+list.size()); deleteFlag = true; } } catch (InterruptedException e) { e.printStackTrace (); } } } };
The idea is this, you can't move to the delete element in wait(), and the loop list.size > 0 && executes the deletion without executing the operation and can only execute it once, which solves this problem.
Summary :
When the wait condition changes, the basic idea is to use the while method to keep the thread waiting, and execute the next step when the condition is met.