天天看点

正确理解wait()和notify()方法

对于初学者来说,下面这个例子是一个非常常见的错误。

/** 
 *  线程A: 循环50次后等待并放弃锁,让线程B执行。 
 */  
class ThreadA extends Thread{  
     //线程同步的公共数据区     
    Object oa=null;  
      
       ThreadA(Object o){  
        this.oa=o;  
    }  
    //线程A执行逻辑  
    public void run(){  
                //线程同步区域,需要申请公共数据的锁  
        synchronized(oa){  
            System.out.println("ThreadA is running......");  
            for(int i=0;i<100;i++){  
                System.out.println("   ThreadA value is "+i);  
                if(i==50){  
                    try {  
                                               //当前线程等待  
                        Thread.currentThread().wait();  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                }//if(i==50)  
            }//for(int i)  
        }  
    }  
}  
/** 
 *  线程B:等待线程A放弃锁,然后获得锁并执行,完成后唤醒线程A 
 */  
class ThreadB extends Thread{  
     //线程同步的公共数据区     
    Object ob=null;  
      
    ThreadB(Object o){  
        this.ob=o;  
    }  
    //线程B执行逻辑  
    public void run(){  
        //线程同步区域,需要申请公共数据的锁  
               synchronized(ob){  
            System.out.println("ThreadB is running......");  
            for(int i=0;i<50;i++){  
                System.out.println("   ThreadB value is "+i);  
            }  
                      //唤醒等待的线程  
            notify();  
        }  
    }  
}  
//测试  
public class ThreadTest {    
     public static void main(String[] args){  
         Object lock=new Object(); //公共数据区  
         ThreadA threada=new ThreadA(lock);  
         ThreadB threadb=new ThreadB(lock);  
         threada.start(); //线程A执行  
         threadb.start(); //线程B执行  
     }  
}    
           

程序很简单,就是让线程 A,B 交替打印。但是运行的时候会抛出两个异常:

Exception in thread "Thread-0" java.lang.IllegalMonitorStateException: current thread not owner

Exception in thread "Thread-1" java.lang.IllegalMonitorStateException: current thread not owner

     问题就处在 ThreadA 中的 Thread.currentThread().wait();  和ThreadB中的 notify(); 上。

     初学者理解 wait() 的时候都认为是将当前线程阻塞,所以 Thread.currentThread().wairt(); 似乎很有道理。但是不知道大家有没有发现,在JDK类库中 wait() 和 notify()方法并不是 Thread 类的,而是 Object 类的。我们仔细看看 wait 方法的JDK文档:

    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

 - 如果在当前线程等待通知之前或者正在等待通知时,任何线程中断了当前线程。在抛出此异常时,当前线程的中断状态 被清除。

    看完JDK文档以后,很显然,只要把开始的程序中 Thread.currentThread().wait() 改成oa.wait() 。 notify() 改成 ob.notify() 就没有问题了。

    也就是说,只能通过同步块 obj 来调用 wait/notify 方法 ,而不能通过想当然的线程调用这两个方法。至于为什么是这样,我有一种想法,大家可以一起讨论一下:

    首先,我们都知道 JVM 会给每一个对象都分配唯一的一把锁。这把锁是在对象中的。

    然后,当 Thread-0 线程获得了这把锁后,应该是在对象中的锁内记录下当前占有自己的线程号,并把自己设置为已被占用。那么当 Thread-0 需要放弃锁的时候,锁对象会把 Thread-0 放入到锁的等待队列中 。而这一切和 Thread-0 是没有任何关系的。自然也轮不到 Thread-0 对象来调用某个方法来改变另一个对象中的锁(这一点也说不通,我自己的锁凭什么让你来改)。

    因此,也就出现用改变公共数据区对象的锁的方法是通过共数据区对象本省来调用,和线程对象是没有关系的。

      事实上,每一个同步锁lock下面都挂了几个线程队列,包括就绪(Ready)队列,等待(Waiting)队列等。当线程A因为得不到同步锁 lock ,从而进入的是lock.ReadyQueue(就绪队列),一旦同步锁不被占用,JVM 将自动运行就绪队列中的线程而不需要任何notify()的操作。但是当线程A被 wait() 了,那么将进入lock.WaitingQuene(等待队列),同时如果占据的同步锁也会放弃。而此时如果同步锁不唤醒等待队列中的进程(lock.notify()),这些进程将永远不会得到运行的机会。

      就绪队列和等待队列有很大的不同,这一点要牢记。