wait-notify机制

转载自:http://www.ticmy.com/?p=219
    http://www.ticmy.com/?p=394

  在调用wait、notify的时候,必须先持有锁,且状态变量须由该锁保护,而内置锁对象与内置条件队列对象又是同一个对象。也就是说,要在某个对象上执行wait,notify,先必须锁定该对象,而对应的状态变量也是由该对象锁保护的。如果在调用wait、notify的时候没有持有锁,将会抛出:java.lang.IllegalMonitorStateException
  当在obj对象上调用wait操作的时候,就会释放当前持有的锁,并将线程加入到obj所属的条件队列,然后阻塞,直到有其它线程在该obj上调用了notify/notifyAll操作或阻塞线程被中断或wait超时。
  当调用Object#notify()方法时,会去唤醒对应对象条件队列中的某个线程,至于唤醒的是哪个线程,这是不确定的,选择是任意性的。当调用Object#notifyAll()方法时,会唤醒条件队列中的所有线程。当唤醒一个线程或所有线程时,这个或这些线程需要自动重新获得原先wait时释放的锁,它(们)并不一定立马就能执行,像其它线程一样,需要等待CPU来调度,需要与其它线程竞争执行前需要获得的锁。
  线程的wait操作的典型代码结构如下:

void op() throws InterruptedException {
    synchronized(obj) {
        while(条件不满足) {
            obj.wait();
        }
    }
}

为什么要在循环中wait?有以下几个原因:
  1、一个对象锁可能用于保护多个状态变量,当它们都需要wait-notify操作时,如果不将wait放到while中就有问题。例如,某一对象锁obj保护两种状态变量a和b,当a的条件断言不成立时发生了wait操作,当b的条件断言不成立时也发生了wait操作,两个线程被加入到obj对应的条件队列中,现在若改变状态变量a的某操作发生,在obj上调用了notifyAll操作,obj对应的条件队列里的所有线程均被唤醒,之前等待a的某个或几个线程去判断a的条件断言可能成立了,但b对应的条件断言肯定仍不成立,而此时等待b的线程也被唤醒了,所以需要循环判断b的条件断言是否满足,如果不满足,继续wait。
  2、多个线程wait的同一个状态的条件断言。如Queue场景下,当前队列是空的,多个线程要从里面取元素,于是都wait了。此时另一个线程往里面添加了一个元素,调用了notifyAll操作,唤醒了所有线程,但只有一个线程能拿到那个新加进来的元素,继续走下去,其它的仍需等待。
  3、虚假唤醒。在没有被通知、中断或超时的情况下,线程自动苏醒了。虽然这种情况在实践中很少发生,但是必须通过循环检测条件是否满足的方式来防止其发生,如果不满足该条件,则继续等待。

  notify操作有两个方法可用,nofity和notifyAll,顾名思义,前者每次唤醒一个线程,后者唤醒所有线程。当唤醒所有线程的时候,会增加上下文切换、锁竞争。但很多时候,使用notify是有风险的,多个线程在同一个条件队列里等待不同的条件断言成立,极可能本该唤醒的线程没唤醒。那么什么时候才能用notify呢?
  1、该对象的条件队列只关联了一个条件断言,且线程被唤醒后执行的代码逻辑是相同的;
   2、单进单出。一次notify(这里不是指notify方法)能唤醒的线程至多一个。

有这样一个例子:

class MyThread extends Thread {
    public void run() {  
        System.out.println(getName() + "开始sleep");
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(getName() + "结束sleep");
    }
}
public class TestWaitNotify {  
    public static void main(String[] args) throws Exception {  
        Thread myThread = new MyThread();  
        myThread.start(); 

        synchronized (myThread) {  
            myThread.wait();  
        }

        System.out.println("wait结束.");
    }
}

  myThread执行结束后,main线程中的wait操作就自动退出了。程序里也并没有看到有notify/notifyAll调用。如果将程序改成下面这样:

class MyThread extends Thread {
    public void run() {  
        System.out.println(getName() + "开始sleep");
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(getName() + "结束sleep");
    }
}
public class TestWaitNotify {  
    public static void main(String[] args) throws Exception {  
        Thread myThread = new MyThread();  
        myThread.start(); 

        Object tmp = new Object();
        synchronized (tmp) {  
            tmp.wait();  
        }

        System.out.println("wait结束.");
    }
}

  wait操作作用在一个独立的对象上,而不是像前面那样作用于线程对象上。这时程序就会一直wait下去。这时为什么?原因在于线程终止的时候会调用线程对象notifyAll方法唤醒因为该线程对象而阻塞的线程队列。

还需要注意的是:
  notify()和notifyAll()这两个方法被调用时,有可能没有线程处于等待状态,通知信号过后便丢弃了。这就是我们说的notify通知遗漏问题。threadA还没开始wait的时候,threadB已经notify了,这样,threadB的通知是没有任何响应的,threadA再开始wait,便会一直阻塞等待,直到被别的线程打断。
  在使用线程的等待/通知机制时,一般都要在while循环中调用wait()方法,满足条件时,才让while循环退出,这样一般也要配合使用一个boolean变量(或其他能判断真假的条件),在notify之前改变该boolean变量的值,让wait返回后能够退出while循环(一般都要在wait方法外围加一层while循环,以防止早期通知),或在通知被遗漏后,不会被阻塞在wait方法处。这样便保证了程序的正确性。

你可能感兴趣的:(Java基础)