• 线程等待(wait)、通知(notify)、等待线程结束(join)和谦让(yield)


    线程等待(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高并发程序设计》葛一鸣,郭超 编著;

    作者:Joe
    努力了的才叫梦想,不努力的就是空想,努力并且坚持下去,毕竟这是我相信的力量
  • 相关阅读:
    IIS Post 大小超出允许的限制
    webpack从零开始第1课:安装webpack和webpack-dev-server
    React
    ubuntu 安装nodejs和git
    ubuntu 启用root用户方法
    ubuntu安装最新版node和npm
    MVC Dropdownlist数据绑定 默认值
    C#调用Excel VBA宏[转载]
    git download error processing
    mysqladmin 使用
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/9674480.html
Copyright © 2020-2023  润新知