多线程-等待(Wait)和通知(notify)
1.为了支撑多线程之间的协作,JDK提供了两个非常重要的线程接口:等待wait()方法和通知notify()方法。
这两个方法并不是在Thread类中的,而是输出在Object类。这意味着任何对象都可以调用这两个方法。
这两个方法如下
当在一个对象实例上调用wait()方法后,当前线程就会在这个对象上等待。
比如,在线程A中,调用了obj.wait()方法,那么线程A就会停止继续执行,转为等待状态。等待到何时结束呢?线程A会一直等到其他线程调用了obj.notity()方法为止。
2.wait()方法和notify()方法究竟是如何工作的呢?
如果一个线程调用了object.wait()方法,那么它就会进入object对象的等待队列,这个队列中,可能会有多个线程,因为系统运行多个线程同时等待某一个对象,
当object.notify()方法被调用的时候,它就会从这个等待队列中随机选择一个线程,并进行唤醒。
除notity()方法外,Object对象还有一个类似的notifyAll()方法,它和notity方法的功能基本一致,不同的是,它会唤醒在这个等待队列中所有等待的线程,而不是随机一个。
object.wait()方法并不能随便调用。它必须包含在对象的synchronzied语句中,无论是wait()方法或者notity()方法都需要首先获得目标对象的一个监视器。
如图,其中T1和T2表示两个线程,T1在正确执行wait()方法前,必须获得object对象的监视器,而wait()方法执行之后会释放这个监视器。
这样做的目的是使其他等待在object对象上的线程不至于因为T1的休眠而全部无法正常执行。
线程T2在notity()方法调用前,也必须获得object对象的监视器。此时T1已经释放了这个监视器。所以T2可以顺利获得object对象的监视器。
接着,T2执行了notify()方法尝试唤醒一个等待线程,这里假设唤醒了T1,T1被唤醒后,要做的第一件事并不是执行后续代码,而是要尝试重新
获得object对象的监视器,而这个监视器也正是T1在wait()方法执行前所持有的那个。
如果暂时无法获得,则T1还必须等待这个监视器。当监视器顺利获得后,T1才可以在真正意义上继续执行。
3.为了方便理解,简单的案例:
/** * wait和 notify 的学习 * @author wm * @date 2019/9/18 15:15 */ public class SimpleWN { final static Object object = new Object(); public static class T1 extends Thread{ public void run() { synchronized (object){ System.out.println(System.currentTimeMillis()+":T1 start! "); System.out.println(System.currentTimeMillis()+":T1 wait for object"); try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis()+":T1 end! "); } } } public static class T2 extends Thread{ public void run() { synchronized (object){ System.out.println(System.currentTimeMillis()+":T2 start! notify one thread"); object.notify(); System.out.println(System.currentTimeMillis()+":T2 end! "); try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { Thread t1 = new T1(); Thread t2 = new T2(); t1.start(); t2.start(); } }
上述代码中,开启了T1和T2两个线程。T1执行了object.wait()方法。执行wait()方法前,T1先申请object的对象锁。
因此,在执行object.wait()时,它持有的是object的对象锁的。wait()方法执行后,T1会进行等待,并释放object对象锁。
T2在执行notity()方法之前也会先获得object的对象锁,这里为了让结果明显,特意在notity()方法执行之后,让T2休眠2秒,
这样做可以更明显地说明,T1在得到notity()方法通知后,还是会先尝试重新获得object的对象锁。
执行结果:
1570675715571:T1 start! 1570675715571:T1 wait for object 1570675715571:T2 start! notify one thread 1570675715571:T2 end! 1570675715571:T1 end!
在T2通知T1继续执行后,T1并不能立即继续执行,而是要等待T2释放object的锁,并重新成功获得锁后,才能继续执行;
4.Object.wait()方法和Thread.sleep()方法都可以让线程等待若干时间,除wait()方法可以被唤醒外,
另外一个主要区别就是wait()方法会释放目标对象的锁,而Thread.sleep()方法不会释放任何资源。