线程等待(wait)、通知(notify)
为了支持多线程之间的协作,JDK提供了两个非常重要的接口:线程等待wait()和通知notify()方法。这两个方法并不是在Thread类中的,而是Object类,这也就意味着任何对象都可以调用者两个方法,下面来看这两个方法的签名:
1 public final void wait() throws InterruptedException; 2 3 public final native void notify();
当在一个对象实例上调用wait()方法后,当前线程就会在这个对象上等待。这是什么意思呢?比如:线程A中,调用了obj.wait()方法,那么线程A就会停止继续执行,转为等待状态。等待到什么时候结束呢?线程A一直等到其他线程调用了obj.notify()方法并且刚好唤醒的是线程A,这时,线程A就会继续执行了。在这个过程中,obj对象就俨然成为了多个线程之间的有效通信手段。
wait()和notify()是怎么工作的呢?如下图所示:
如果一个线程调用了obj.wait(),那么它就会进入obj等待队列,在这个队列中,可能会有多个线程,因为有可能系统运行时,存在多个线程等待同一个对象的情况,当其他线程调用obj.notify()时,它就会从这个等待的队列中,随机唤醒一个线程。这里需要注意的是,这个选择是不公平的,并不是先等待的就先被唤醒,这个选择完全是随机的。
除了notify()方法外,object对象还有一个方法是notifyAll(),它与notify()方法基本一致,但不同的是,它会唤醒这个等待队列中所有等待的线程。如下图:
强调:
wait()和notify()方法,都必须包含在对应的同步语句中,因为这两个方法都需要先获取到目标对象的监视器。下面举例说明:
1 public class SimpleWN { 2 //对象 3 final static Object object = new Object(); 4 5 //等待线程 6 public static class Wait implements Runnable{ 7 @Override 8 public void run() { 9 synchronized (object){ 10 System.out.println(System.currentTimeMillis()+": Wait start!"); 11 try { 12 System.out.println(System.currentTimeMillis() + ": Wait is Waiting!"); 13 object.wait(); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 System.out.println(System.currentTimeMillis() + ": Wait end!"); 18 } 19 } 20 } 21 //唤醒线程 22 public static class Notify extends Thread{ 23 @Override 24 public void run() { 25 synchronized (object){ 26 System.out.println(System.currentTimeMillis() + ": Notify is start ! notify one thread!"); 27 object.notify(); 28 System.out.println(System.currentTimeMillis() + ": Notify end!"); 29 try { 30 Thread.sleep(2000); 31 } catch (InterruptedException e) { 32 e.printStackTrace(); 33 } 34 } 35 } 36 } 37 //测试 38 public static void main(String[] args){ 39 Thread t = new Thread(new Wait()); 40 Thread t2 = new Notify(); 41 t.start(); 42 t2.start(); 43 } 44 45 }
输出结果:
1 1537320217650: Wait start! 2 1537320217650: Wait is Waiting! 3 1537320217650: Notify is start ! notify one thread! 4 1537320217650: Notify end! 5 1537320219650: Wait end!
上面的代码中,有两个线程Wait和Notify,Wait执行了object.wait()方法,注意在执行等待方法前,线程Wait先获取到了object对象锁,因此,在执行object.wait()时,线程Wait是持有object的锁的。wait()方法执行后,Wait进入等待状态,并释放object锁,如果不释放锁,线程Notify是不会开始执行的。因为在线程Notify执行前,需要先获取object对象锁。
通过观察输出结果的时间戳,可以看到在线程Notify通知线程Wait继续执行后,线程Wait并没有立即执行,而是等待线程Notify释放object锁后,在重新获得object锁后,才继续执行。在打印出 Notify end! 和 Wait end! 时间差位2S,我们让线程Notify休眠了2S。
注意:
Object.wait()和Thread.sleep()方法都可以让线程等待若干时间,除了wait()可以被唤醒,sleep()不需要唤醒外,另外一个主要的区别就是:wait()方法会释放目标对象的锁,而Thread.sleep()不会释放任何资源。
等待线程结束(join)和谦让(yield)
在很多情况下,一个线程的输入可能非常依赖于另外一个或者多个线程的输出,此时,这个依赖的线程就需要等待目标线程执行完毕,才能继续执行。JDK提供了join()方法来实现这个功能,下面是join()方法的签名:
1 public final void join() throws InterruptedException; 2 public final synchronized void join(long millis) throws InterruptedException;
第一个join()表示无限期等待,它会一直阻塞当前线程,直到目标线程执行完毕。第二个方法给出了一个最大的等待时间,如果超过给定时间目标线程还没有执行完毕,当前线程也会因为等待时间过长,开始继续执行,就是等不及了。
下面举例说明:
1 public class Join { 2 public volatile static int i = 0; 3 //目标线程 4 public static class AddThread implements Runnable{ 5 @Override 6 public void run() { 7 for (i = 0; i < 10000;i++){ 8 if (i == 9999){ 9 System.out.println("自增结束,i = " + i); 10 } 11 } 12 } 13 } 14 15 public static void main(String[] args) throws InterruptedException { 16 Thread thread = new Thread(new AddThread()); 17 thread.start(); 18 thread.join(); 19 System.out.println(i); 20 } 21 }
输出结果:
1 自增结束,i = 9999 2 10000
在main方法中,如果不使用join()方法,那么得到的i就是0,因为i为静态的全局变量,目标线程还没有开始i就被输出了,但是加上join()方法后,main线程会等待目标线程执行完毕后才会继续执行,故i输出总是10000。
下面来看看join()方法的源码,进一步了解:
join():
public final void join() throws InterruptedException { join(0); }
join(long millis):
1 public final synchronized void join(long millis) 2 throws InterruptedException { 3 long base = System.currentTimeMillis(); 4 long now = 0; 5 6 if (millis < 0) { 7 throw new IllegalArgumentException("timeout value is negative"); 8 } 9 10 if (millis == 0) { 11 while (isAlive()) { 12 wait(0); 13 } 14 } else { 15 while (isAlive()) { 16 long delay = millis - now; 17 if (delay <= 0) { 18 break; 19 } 20 wait(delay); 21 now = System.currentTimeMillis() - base; 22 } 23 } 24 }
isAlive():
1 /** 2 * Tests if this thread is alive. A thread is alive if it has 3 * been started and has not yet died. 4 * 5 * @return <code>true</code> if this thread is alive; 6 * <code>false</code> otherwise. 7 */ 8 public final native boolean isAlive();
可以看到,join()无参方法,就是调用join(long millis),只不过时间为0.所以只看join(long millis)方法就行了。
通过源码可以看到,join()方法的本质是让调用线程wait()在当前线程对象的实例上。通过查看代码第 11 行,可以看出,这段代码是让调用线程在当前线程上进行等待,当线程执行完成后,被等待的线程会在退出前调用noyifyAll()方法通知所有等待的线程继续执行;从isAlive()方法的注释可以看出,如果线程是活的,返回true,反之,返回false;
注意:
在开发中,不要在Thread对象实例上使用类似wait()和notify()等方法,因为这样有可能会影响系统的API工作,或者被系统的API影响。
yield():
yield()签名:
public static native void yield();
这是一个静态的本地方法,一旦执行,它会使当前线程让出CPU的资源。但是要注意,让出CPU资源并不代表当前线程不执行了。当前线程让出CPU资源后,还会进行CPU资源的争夺,但是能否再次被分配到资源就不一定了。
使用场景:
如果你觉得一个线程不那么重要,或者优先级非常低,并且害怕它占用太多的CPU资源,那么可以在适当的时候调用该方法Thread,yield(),给予其他重要线程更多的执行机会。
参考:《Java高并发程序设计》葛一鸣,郭超 编著;