线程之间协作----等待与通知

在理解了线程之间可能存在相互冲突,以及怎样避免冲突之后,下一步就是学习怎样使线

程之间相互协作。这种协作关键是通过线程之间的握手来进行的,这种握手可以通过

Object的方法wait( )和notify()来安全的实现。

调用sleep()的时候锁并没有被释放,理解这一点很重要。另一方面,wait( )方法的

确释放了锁,这就意味着在调用wait()期间,可以调用线程中对象的其他同步控制方法。

当一个线程在方法里遇到了对wait()的调用的时候,线程的执行被挂起,对象上的锁被

释放。

 

有两种形式的wait( )。第一种接受毫秒作为参数,意思与 sleep()方法里参数的意思

相同,都是指“在此期间暂停”   。不同之处在于,对于 wait():

 

1.在 wait( )期间锁是释放的。

 

2.你可以通过notify( )、notifyAll(),或者时间到期,从wait( )中恢

复执行。

 

第二种形式的wait( )不要参数;这种用法更常见。wait( )将无限等待直到线程接收到

notify( )或者notifyAll( )消息。

 

wait( ), notify( ),以及notifyAll( )的一个比较特殊的方面是这些方法是基类

Object的一部分,而不是像Sleep( )那样属于Thread的一部分。尽管开始看起来有点

奇怪,仅仅针对线程的功能却作为通用基类的一部分而实现,不过这是有道理的,因为这

些功能要用到的锁也是所有对象的一部分。所以,你可以把wait( )放进任何同步控制方

法里,而不用考虑这个类是继承自Thread还是实现了Runnable接口。实际上,你只能在

同步控制方法或同步控制块里调用wait(), notify( )和notifyAll()(因为不用

操作锁,所以sleep()可以在非同步控制方法里调用)。如果你在非同步控制方法里调用

这些方法,程序能通过编译,但运行的时候,你将得到IllegalMonitorStateException

异常,伴随着一些含糊的消息,比如“当前线程不是拥有者”。消息的意思是,调用 wait( ),

notify( )和notifyAll( )的线程在调用这些方法前必须“拥有”(获取)对象的锁。

 

你能够让另一个对象执行这种操作以维护其自己的锁。要这么做的话,你必须首先得到对

象的锁。比如,如果你要在对象x上调用notify( ),那么你就必须在能够取得x的锁的同

步控制块中这么做:

 

synchronized(x) {

  x.notify();

}

特别地,当你在等待某个条件,这个条件必须由当前方法以外的因素才能改变的时候(典

型地,这个条件被另一个线程所改变),就应该使用wait( )。你也不希望在线程里测试

条件的时候空等;这也称为“忙等”,它会极大占用CPU时间。所以wait( )允许你在等待

外部条件的时候,让线程休眠,只有在收到notify( )或notifyAll()的时候线程才唤

醒并对变化进行检查。所以,wait()为在线程之间进行同步控制提供了一种方法。

 

例如,考虑一个餐馆,有一个厨师和一个服务员。服务员必须等待厨师准备好食物。当厨

师准备好食物的时候,他通知服务员,后者将得到食物然后继续等待。这是一个线程协作

的极好的例子:厨师代表了生产者,服务员代表了消费者。

WaitPerson(服务员)必须知道自己所工作的Restaurant(餐馆),因为他们必须从餐馆

的“订单窗口”取出订单restaurant.order。在run( )中,WaitPerson调用wait( )

进入等待模式,停止线程的执行直到被Chef(厨师)的notify( )方法所唤醒。因为是很

简单的程序,我们知道只有一个线程在等待WaitPerson对象的锁:即WaitPerson线程

自己。正因为这个原因,使得调用notify()是安全的。在更复杂的情况下,多个线程可

能在等待同一个特定的锁,所以你不知道哪个线程被唤醒。解决方法是调用notifyAll( ),

它将唤醒所有等待这个锁的线程。每个线程必须自己决定是否对这个通知作出反应。

 

注意对wait( )的调用被包装在一个while( )语句里,它在测试的正是等待的条件。开

始看起来可能很奇怪--如果你在等一个订单,那么一旦你被唤醒,订单必须是可用的,

对不对?问题是在多线程程序里,一些别的线程可能在WaitPerson苏醒的同时冲进来抢

走订单。唯一安全的方法就是对于wait()总是使用如下方式:

 

while(conditionIsNotMet)

wait( );

 

这可以保证在你跳出等待循环之前条件将被满足,如果你被不相干的条件所通知(比如

notifyAll( )),或者在你完全退出循环之前条件已经被改变,你被确保可以回来继续

等待。

 

一个Chef对象必须知道他/她工作的餐馆(这样可以通过restaurant.order下订单)和

取走食物的WaitPerson,这样才能在订单准备好的时候通知WaitPerson。在这个简化

过的例子中,由Chef产生订单对象,然后通知WaitPerson订单已经准备好了。

 

请注意对notify()的调用必须首先获取WaitPerson对象的锁。WaitPerson.run( )

里对wait( )的调用将自动释放这个锁,所以这是可能的。因为要调用notify( )必须获

取锁,这就能保证如果两个线程试图在同一个对象上调用notify( )时不会互相冲突。

 

上面的例子中,一个线程只有一个单一的地点来存储某个对象,这样另一个线程就可以在

以后使用这个对象。然而,在一个典型的生产者-消费者实现中,你要使用先进先出的队

列来存放被生产和消费的对象。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值