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 方法详情可以参考 我的另一篇博文,如果有什么错误欢迎斧正,或有不明白的地方可以留联系方式交流交流。

  • 相关阅读:
    SQL Server 数据类型映射 (ADO.NET)
    微软SQLHelper.cs类 中文版
    在WinForm中使用Web Service来实现软件自动升级
    Winform开发框架之通用自动更新模块(转)
    C# winform 最小化到电脑右下角
    3层数据访问架构(部分)
    Castle ActiveRecord学习实践
    .Net下的 ORM框架介紹
    配置企业库5.0管理
    一个简洁通用的调用DLL函数的帮助类
  • 原文地址:https://www.cnblogs.com/keZhenxu94/p/5288477.html
Copyright © 2011-2022 走看看