线程中的wait和notify方法

synchronize 保证了多线程并发时
线程间的互斥行
代码块的原子性
变量的可见性
但是没有提供方法实现线程间的同步通信机制
而wait(),notify()和notifyall()方法是java.lang.Object类为线程提供的用于实现线程间通信的同步控制方法
 
通常,多线程之间需要协调工作。例如,浏览器的一个显示图片的线程displayThread想要执行显示图片的任务,必须等待下载线程 downloadThread将该图片下载完毕。
如果图片还没有下载完,displayThread可以暂停,当downloadThread完成了任务 后,再通知displayThread“图片准备完毕,可以显示了”,这时,displayThread继续执行。
以上逻辑简单的说就是:如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于wait/notify。等待机制与锁机制是密切关联的。
 
例如:
synchronized(obj) {
while(!condition) {
obj.wait();
}
obj.doSomething();
}  

当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait()。
在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,
就可以唤醒线程A:
 
synchronized(obj) {
condition = true;
obj.notify();


需要注意的概念是:

  • 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {...} 代码段内。
  • 调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj) {...} 代码段内唤醒A。
  • 当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。
  • 如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。
  • obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。
  • 当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。
 
wait()方法使当前线程主动释放互斥锁,并进入该互斥锁的等待队列。
(也就是说,它使当前线程暂停执行,等待其他线程执行notify()方法或者notifyall()方法后再竞争该锁,如果成功就继续执行本线程,否则等待下次唤醒)
 
notify()系列方法,用于释放一个项目的线程,唤醒另一个可能在等待的线程.
 
线程等待有两种调用格式:
1. wait()等待通信线程唤醒后再继续执行本线程,没唤醒的时候处于sleeping状态,不会被分布CPU,也不会主动的竞争锁.
2. wait(long millis)等待通信线程唤醒或者最多等待millis毫秒后,自动唤醒,然后尝试竞争对象的锁,如果不成功就进步blocking状态.

调用wait()和notify()系列方法时,当前线程必须拥有此对象监视器(即对象锁)。如果当前线程不是此对象监视器的所有者,会抛IllegalMonitorStateException。
通过以下三种方法之一,线程可以成为对象监视器的所有者:
    * 通过执行此对象的同步实例方法。
    * 通过执行在此对象上进行同步的 synchronized 语句的正文。
    * 对于 Class 类型的对象,可以通过执行该类的同步静态方法。 
 
注意1:对于一个对象,某一时刻只能有一个线程拥有该对象的监视器。 

public final void wait() throws InterruptedException

    在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
    当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,
    或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。
    对于某一个参数的版本,实现中断和虚假唤醒是可能的,而且此方法应始终在循环中使用:

    synchronized (obj) {
    while (<condition does not hold>)
    obj.wait();
    ... // Perform action appropriate to condition
         }

    此方法只应由作为此对象监视器的所有者的线程来调用。
    抛出:
        IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。 
        InterruptedException - 如果在当前线程等待通知之前或者正在等待通知时,任何线程中断了当前线程。
        在抛出此异常时,当前线程的中断状态 被清除。
注意1:
wait()方法后,当前线程暂停执行,线程进入sleep状态,cpu不会分给其时间,等待其他线程执行notify()方法或者notifyall()方法或用interrupt()取消后再继续执行本线程
它还有种调用格式:wait(long millis)等待通信线程唤醒或者最多等待millis毫秒后,再继续执行本线程。
    
注意2:wait()方法使当前线程主动释放互斥锁.
注意3:线程A调用了wait()进入了等待状态,也可以用interrupt()取消.详细参考《线程的interrupt》
注意4:对于某一个参数的版本,实现中断和虚假唤醒是可能的,而且此方法应始终在循环中使用。

public final void notify()
    唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。
    选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
    直到当前线程放弃此对象上的锁定(也就是完成synchronized 代码块),才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;
    例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。 

注意1:
被notify()唤醒的线程,也只有在当前线程结束Synchronized代码块,放弃此对象上的锁定后,才可能被执行.
它需要以常规方式与在该对象上主动synchronized的线程进行锁竞争,竞争成功了还能被执行.

public final void notifyAll()
    唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
    直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;
    例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。
    此方法只应由作为此对象监视器的所有者的线程来调用。
    抛出:IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。
       
注意1:
被notify()唤醒的线程,也只有在当前线程介绍Synchronized代码块,放弃此对象上的锁定后,才可能被执行.
它需要以常规方式与在该对象上主动synchronized的线程进行锁竞争,竞争成功了还能被执行.

wait(),notify(),notifyall()所在对象和锁是绑定的,它们是一一对应的,因此它的wait(),notify(),notifyall()都是操作同一个对象.
但是我们只想通过notify(),notifyall()来对一些wait()线程而不是该锁上所有wait()的线程进行唤醒时。(这样的效率要高很多,当然应该不只是可以得到效率的好处)
synchronized和wait(),notify(),notifyall()就显得苍白无力。幸好java1.5中提供了ReentrantLock,ReadLock,WriteLock来和Condition配套使用以实现这种功能。

示例1
class BoundedBuffer {
       final String[] items = new String[100];
       int putptr, takeptr, count;
       final String conditionPut="put";
       final String conditionTake="take";
       synchronized public void put(String x) throws InterruptedException {
           while (count == items.length)
           {
               System.out.println("It is full.the action of put is wait");
               synchronized(conditionPut)
               {
                   conditionPut.wait();//@1
               }
           }
           items[putptr] = x;
           if (++putptr == items.length) putptr = 0;
           ++count;
           synchronized(conditionTake)
           {
               conditionTake.notify();
           }
       }

       synchronized public String take() throws InterruptedException {
           while (count == 0)
           {
               System.out.println("It is empty.the action of take is wait");
               synchronized(conditionTake)
               {
                   conditionTake.wait();//@1
               }
           }
           String x = items[takeptr];
           if (++takeptr == items.length) takeptr = 0;
           --count;
           synchronized(conditionPut)
           {
               conditionPut.notify();
           }
           return x;
       }
     }
注意:
在@1处无法进行当前线程对this的锁的释放,当然别的线程也就无法进入put和take,线程就死锁了。
 
实例1:
因为wait(),notify(),notifyall()所在对象和锁是绑定的notify(),notifyall()是对对象(锁)上所有wait()的线程进行唤醒(notify是随机选一个)
所以只能用下面的方式。
class BoundedBuffer {
       final String[] items = new String[100];
       int putptr, takeptr, count;
       synchronized public void put(String x) throws InterruptedException {
           while (count == items.length)@5
           {
               System.out.println("It is full.the action of put is wait");
               this.wait();//@1
           }
           items[putptr] = x;
           if (++putptr == items.length) 
           {
               putptr = 0;
            }
           ++count;
           this.notify();@2
       }

       synchronized public String take() throws InterruptedException {
           while (count == 0)//@6
           {
               System.out.println("It is empty.the action of take is wait");
               this.wait();//@3
           }
           String x = items[takeptr];
           if (++takeptr == items.length) 
           {
                   takeptr = 0;
           }
           --count;
           this.notify();//@4
           return x;
       }
     }
注意:
虽然我们希望的是@1处由@4出来唤醒,@3处由@2出来唤醒。但是notify(),notifyall()是对锁上所有wait()的线程进行唤醒(notify是随机选一个)。
因此@2出也可能唤醒@1,所以@5出要用while来判断是否真的满足条件了,@6同理。
posted @ 2014-10-06 16:12  princessd8251  阅读(3190)  评论(0编辑  收藏  举报