书版本:2015年11月第1版第3次印刷
问题概述:
3.1.10 主要讲解 等待wait的条件发生变化的场景
为了方便起见,我就不照抄书中原码了,我用我自己的代码 就是一个main方法 我用的jdk1.6
public static void main(String[] args) throws InterruptedException { final List<String> list = new ArrayList<String>(); final Object lock = new Object(); // 等待&删除 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(); } } } }; // 唤醒&增加 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"); } } }; //线程1-删除操作 锁等待 Thread waitT1 = new Thread( waitRun ); waitT1.start(); //线程2-删除操作 锁等待 Thread waitT2 = new Thread( waitRun ); waitT2.start(); Thread.sleep( 1000L ); //线程3-增加操作 唤醒所有等待 Thread notifyT1 = new Thread( notifyRun ); notifyT1.start(); }
这样有个问题就如书中据说 会有一个删除操作 异常 因为已无元素可删。
Exception in thread "Thread-0" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
书中给出的解决方案是这样的
是在删除线程中 while(list.size==0) 这样可以保证在有元素的时候执行删除操作。
// 等待&删除 Runnable waitRun = new Runnable() { @Override public void run() { synchronized( lock ){ try { // 此处是修改点 由原来的if 改成 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(); } } } };
这样确实保证了有元素时才删除,因为当你无元素时,会一直循环wait()操作。
但这有个问题是第二个删除线程 因为集合已无元素删除了,会多一个wait,而这个wait()是在notifyAll之产生的,会永远唤醒不了。
所以这个做法属于解决了旧问题,又产生新的问题。
而我的做法是这样的
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()); // 是否执行过删除 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(); } } } };
思路是这样的,不能在wait()动手脚,转移到删除元素上,循环list.size >0 && 没有执行过操作 才执行删除并且只能执行一次 这样就解决了这个问题了。
总结:
遇到wait条件变化时,基本思路就是用while的方法让线程一直处于等待,待条件满足时才执行下一步。