线程之间的协作
@(并发)[java, 并发, Thinking in Java]
1. wait() 与 notifyAll()
waint()使得可以等待某个条件发生,而改变这个条件超出了当前方法的控制能力。wait()会在等待外部世界产生变化时将任务挂起,对象上的锁释放。与此相反,调用sleep()和yield()时锁并没有被释放
wait()区别与sleep():
在wait()期间对象锁是释放的
可以通过notify(), notifyAll(), 或者令时间到期,从wait()中恢复执行
有两种方式的wait(),第一种啊方式接受毫秒数作为参数,第二种不接受任何参数,这种wait()将无限等待下去,直到接收到notify()/notifyAll()
wait(),notify(), notifyAll()这些方法是基类Object的一部分,而不属于Thread,因为这些方法操作的锁也是所有对象的一部分,比如sleep()不需要操作锁,所以是Thread的方法。
所以可以把wait()放进任何同步控制方法中,而不用考虑这个类是继承自Thread还是实现了Runnable接口。
实际上,只能在同步控制方法或同步控制块中调用这些方法,否则程序能通过编译,但在运行时将得到IllegalMonitorStateException异常。
见《Thinking in Java》P704实例
**必须用一个检查感兴趣的条件的while循环包围wait(),原因如下:
1. 可能有多个任务出于相同的原因在等待同一个锁,而第一个唤醒任务可能会改变这种状况,在这种情况下,这个任务应该被再次挂起,
2. 在这个任务从其wait()中被唤醒的时刻,有可能会有其它某个任务已经做出改变,从而使得这个任务在此时不能执行,或者执行某个操作已显得无关紧要,此时应该重新挂起
3. 也有可能某些任务出于不同的原因在等待这个对象上的锁(在这种情况下必须使用notifyAll()),此时必须检查是否已经由正确的原因唤醒,否则挂起
1.1. 错失的信号
while必须放在同步方法或者同步块中,否则可能会错过某个信号
2. notify() 与 notifyAll()
使用notify()时,在众多等待同一个锁的任务中只有一个会被唤醒,所以必须保证被唤醒的是恰当的任务,且所有的任务必须等待相同的条件。
notifyAll()因某个特定的锁而被调用时,只有等待这个锁的任务才会被唤醒。
当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。
notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。
3. 生产者与消费者
《Thinking in Java》P709饭店、厨师、服务员实例
ExecutorService发送interrupt()后,如果是sleep这种可中断的操作,会抛出InterruptedException,如果是不可中断的操作,需要通过Thread.interrupted()测试而退出,详见P711
3.2. 使用显式的Lock和Condition对象
java.util.concurrent中,使用互斥并允许任务挂起的基类是Condition,可以通过Condition上调用await()来挂起一个任务,当某个任务应该继续执行时,可以调用signal()来唤醒一个任务,或者调用signalAll()来唤醒所有在这个Condition上被其自身挂起的任务。
private Lock lock = new Reentrantlock();
private Condition condition = lock.newCondition();
condition.await();
condition.signalAll();
3.3. 生产者-消费者与队列
可以使用同步队列来解决任务协作问题java.util.concurrent.BlockingQueue接口
LinkedBlockingQueue
: 具有固定尺寸,可以放置有限数量的元素
ArrayBlockingQueue
:无界队列
同步队列在任何时刻都只允许一个任务插入或移除元素。如果消费者任务试图从队列中获取元素,而该队列此时为空,那么这些队列还可以挂起消费者任务,并且当有更多的元素可用时恢复消费者任务。
3.4. 任务间使用管道进行输入/输出
在引入同步队列前的用法
PipedWriter类:允许任务向管道写
PipedReader类:允许不同任务从同一个管道中读取
private PipedWriter out= new PipedWriter();
out.write();
private PipedReader in = new PipedReader(out);
in.read();