Welcome 微信登录

首页 / 软件开发 / JAVA / Java线程机制(五) 等待与通知机制

Java线程机制(五) 等待与通知机制2013-12-07 cnblogs 文酱在之前我们关于停止Thread的讨论中,曾经使用过设定标记done的做法,一旦done设置为true,线程就会 结束,一旦为false,线程就会永远运行下去。这样做法会消耗掉许多CPU循环,是一种对内存不友好的行为。

java中的对象不仅拥有锁,而且它们本身就可以通过调用相关方法使自己成为等待者和通知者。

Object对象本身有两个方法:wait()和notify()。wait()会等待条件的发生,而notify()会通知正在 等待的线程此条件已经发生,它们都必须从synchronized方法或块中调用。

这种等待-通知机制的目的 究竟是为何?

等待-通知机制是一种同步机制,但它更像是一个通信机制,能够让一个线程与另一个线 程在某个特定条件下进行通信。但是,该机制却没有指定特定条件是什么。

等待-通知机制能否取代 synchronized机制吗?当然不行,等待-通知机制并不会解决synchronized机制能够解决的竞争问题,实际上 ,这两者是相互配合使用的,而且它本身也存在竞争问题,这是需要通过synchronzied来解决的。

private boolean done = true;public synchronized void run(){while(true){try{ if(done){ wait(); }else{ repaint(); wait(100); }}catch(InterruptedException e){return;}}}public synchronized void setDone(boolean b){ done = b; if(timer == null){timer = new Thread(this);timer.start(); } if(!done){notify(); }}
这里的done已经不是volatile,因为我们不只是设定个标记值,我们还需要在设定标记的同时自 动发送一个通知。所以,我们现在是通过synchronized来保护对done的访问。

run()方法不会在done为 false时自动退出,它会通过调用wait()方法让线程在这个方法中等待,直到其他线程调用notify()方法。

这里有几个地方值得我们注意。

首先,我们这里通过使用wait()方法而不是sleep()方法来使 线程休眠,因为wait()方法需要线程持有该对象的同步锁,当wait()方法执行的时候,该锁就会被释放,而当 收到通知的时候,线程需要在wait()方法返回前重新获得该锁,就好像一直都持有锁一样。这个技巧是因为在 设定与发送通知以及测试与取得通知之间是存在竞争的,如果wait()和notify()在持有同步锁的同时没有被调 用,是完全没有办法保证此通知会被接收到的,并且如果wait()方法在等待前没有释放掉锁,是不可能让 notify()方法被调用到,因为它无法取得锁,这也是我们之所以使用wait()而不是sleep()的另一个原因。如 果使用sleep()方法,此锁就永远不会被释放,setDone()方法也永远不会执行,通知也永远不会送出。

接着就是这里我们对run()进行同步化。我们之前讨论过,对run()进行同步是非常危险的,因为run() 方法是绝对不可能会完成的,也就是锁永远不会被释放,但是因为wait()本身就会释放掉锁,所以这个问题也 被避免了。

我们会有一个疑问:如果在notify()方法被调用的时候,没有线程在等待呢?

等待 -通知机制并不知道所送出通知的条件,它会假设通知在没有线程等待的时候是没有被收到的,因为这时它也 只是返回且通知也被遗失掉,稍后执行wait()方法的线程就必须等待另一个通知。

上面我们讲过,等 待-通知机制本身也存在竞争问题,这真是一个讽刺:原本用来解决同步问题的机制本身竟然也存在同步问题 !其实,竞争并不一定是个问题,只要它不引发问题就行。我们现在就来分析一下这里的竞争问题:

使用wait()的线程会确认条件不存在,这通常是通过检查变量实现的,然后我们才调用wait()方法。当其他线 程设立了该条件,通常也是通过设定同一个变量,才会调用notify()方法。竞争是发生在下列几种情况:

1.第一个线程测试条件并确认它需要等待;

2.第二个线程设定此条件;

3.第二个线程 调用notify()方法,这并不会被收到,因为第一个线程还没有进入等待;

4.第一个线程调用wait()方 法。

这种竞争就需要同步锁来实现。我们必须取得锁以确保条件的检查和设定都是automic,也就是说 检查和设定都必须处于锁的范围内。

既然我们上面讲到,wait()方法会释放锁然后重新获取锁,那么 是否会有竞争是发生在这段期间呢?理论上是会有,但系统会阻止这种情况。wait()方法与锁机制是紧密结合 的,在等待的线程还没有进入准备好可以接收通知的状态前,对象的锁实际上是不会被释放的。

我们 的疑问还在继续:线程收到通知,是否就能保证条件被正确的设定呢?抱歉,答案不是。在调用wait()方法前 ,线程永远应该在持有同步锁时测试条件,在从wait()方法返回时,该线程永远应该重新测试条件以判断是否 还需要等待,这是因为其他的线程同样也能够测试条件并判断出无需等待,然后处理由发出通知的线程所设定 的有效数据。但这是在只有一个线程在等待通知,如果是多个线程在等待通知,就会发生竞争,而且这是等待 -通知机制所无法解决的,因为它能解决的只是内部的竞争以防止通知的遗失。多线程等待最大的问题就是, 当一个线程在其他线程收到通知后再收到通知,它无法保证这个通知是有效的,所以等待的线程必须提供选项 以供检查状态,并在通知已经被处理的情形下返回到等待的状态,这也是我们为什么总是要将wait()放在循环 里面的原因。

wait()也会在它的线程被中断时提前返回,我们的程序也必须要处理该中断。