zoukankan      html  css  js  c++  java
  • Java 线程 join 方法详解 -- ISS(Ideas Should Spread)

    本文是笔者 Java 学习笔记之一,旨在总结个人学习 Java 过程中的心得体会,现将该笔记发表,希望能够帮助在这方面有疑惑的同行解决一点疑惑,我的目的也就达到了。欢迎分享和转载,转载请注明出处,谢谢合作。由于笔者水平有限,文中难免有所错误,希望读者朋友不吝赐教,发现错漏我也会更新修改,欢迎斧正。(可在文末评论区说明或索要联系方式进一步沟通。)

    首先可以由一个面试题来引出 join 方法的使用,题目如下:

    现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?

    这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对 join 方法是否熟悉。这个多线程问题比较简单,可以用 join 方法实现。如:

    public class ThreadJoin {
    
        public static void main (String[] args) throws InterruptedException {
            final Thread1 thread1 = new Thread1 ();
            final Thread2 thread2 = new Thread2 ();
            final Thread3 thread3 = new Thread3 ();
            thread1.start ();
            thread1.join ();
            thread2.start ();
            thread2.join ();
            thread3.start ();
            thread3.join ();
        }
    }
    
    class Thread1 extends Thread {
        @Override
        public void run () {
            for (int i = 0; i < 10; i++) {
                System.out.print (i + " ");
            }
        }
    }
    
    
    class Thread2 extends Thread {
        @Override
        public void run () {
            for (int i = 10; i < 20; i++) {
                System.out.print (i + " ");
            }
        }
    }
    
    
    class Thread3 extends Thread {
        @Override
        public void run () {
            for (int i = 20; i < 30; i++) {
                System.out.print (i + " ");
            }
        }
    }
    

    程序让第一个线程打印 0 到 9, 第一个线程打印 10 到 19,第三个线程打印 20 到 29,以上代码总是能够连续输出 0 到 29,如果将 join 方法的调用去掉则根据每次运行结果都可能不一致。

    那么为什么 join 方法能够保证线程的顺序运行呢,先来看看 JDK 源码:

    /**
     * Waits for this thread to die.
     *
     * <p> An invocation of this method behaves in exactly the same
     * way as the invocation
     *
     * <blockquote>
     * {@linkplain #join(long) join}{@code (0)}
     * </blockquote>
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public final void join() throws InterruptedException {
        join(0);
    }
    
    /**
    * Waits at most {@code millis} milliseconds for this thread to
    * die. A timeout of {@code 0} means to wait forever.
    *
    * <p> This implementation uses a loop of {@code this.wait} calls
    * conditioned on {@code this.isAlive}. As a thread terminates the
    * {@code this.notifyAll} method is invoked. It is recommended that
    * applications not use {@code wait}, {@code notify}, or
    * {@code notifyAll} on {@code Thread} instances.
    *
    * @param  millis
    *         the time to wait in milliseconds
    *
    * @throws  IllegalArgumentException
    *          if the value of {@code millis} is negative
    *
    * @throws  InterruptedException
    *          if any thread has interrupted the current thread. The
    *          <i>interrupted status</i> of the current thread is
    *          cleared when this exception is thrown.
    */
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    

    从注释中可以看到:

    Waits for this thread to die.

    也就是说调用这个线程(A)的 join 方法的那个线程(B)会等到这个线程(A) die,那么线程 B 进入等待状态直到 线程 A 死亡也就保证了 B 必须在 A 执行完毕才接着执行。

    从源码角度可以看到 join 方法的功能,从使用角度来讲也许已经足够,如果要探究原理的话,源代码中也能提供一些线索,观察 join 实现源码可以发现其中最重要的就是使用 wait 方法了,如果对 wait 方法有所了解的话 join 的实现原理也不难理解了,原理如下:

    • 首先发现 join 方法使用了 synchronized 关键字修饰,也就是说在对线程(假设为 thread1) 调用该方法时候(即 thread1.join()),调用该方法的调用线程(如 main 主线程)会在拥有 thread1 的对象锁之后进入方法体执行;
    • 进入方法体后,一个 while(isAlive()) 循环会持续判断被调用的那个线程(thread1)是否还存活,关于 isAlive 的方法如下:

      /**
       * Tests if this thread is alive. A thread is alive if it has
       * been started and has not yet died.
       *
       * @return  <code>true</code> if this thread is alive;
       *          <code>false</code> otherwise.
       */
      public final native boolean isAlive();
      

      可以看出两种情况下该循环会退出:

      • 线程 thread1 还没有开始,即还没有调用 thread1.start()
      • 线程 thread 已经死亡,即线程 die
    • 如果循环条件不满足,将会调用 wait(delay) 方法,注意此时相当于 this.wait(delay),而 this 代表被调用的线程对象(即 thread1),也就是调用线程(main 主线程)被加入 thread1 线程的对象等待集合,此时调用线程阻塞,thread1 会被调度(假设没有其它线程),循环一直重复直到 thread1 运行完毕循环条件不满足,调用线程(main 主线程)退出 join 方法,从调用 join 方法的下一行代码接着运行。实现线程的顺序执行。

    关于 wait 方法

    关于 wait 方法详情可以参考 我的另一篇博文,如果有什么错误欢迎斧正,或有不明白的地方可以留联系方式交流交流。

  • 相关阅读:
    城市的划入划出效果
    文本溢出省略解决笔记css
    长串英文数字强制折行解决办法css
    Poj 2352 Star
    树状数组(Binary Indexed Trees,二分索引树)
    二叉树的层次遍历
    Uva 107 The Cat in the Hat
    Uva 10336 Rank the Languages
    Uva 536 Tree Recovery
    Uva10701 Pre, in and post
  • 原文地址:https://www.cnblogs.com/keZhenxu94/p/5288477.html
Copyright © 2011-2022 走看看