zoukankan      html  css  js  c++  java
  • 由Thread.join引发的思考

    下面是一段司空见惯的代码,创建两个线程A和线程B,使得线程A优先于线程B执行,使得线程B优先于主线程执行

    public class Demo52 {
        public static void main(String[] args) {
             
            Thread thread1 = new Thread(()->{
                System.out.println("线程:"+Thread.currentThread().getName());
            },"A");
            Thread thread2 = new Thread(()->{
                try {
                    thread1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:"+Thread.currentThread().getName());
            },"B");
            thread1.start();
            thread2.start();
            try {
                thread2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main线程");
        }
    }
    

    输出结果:

    线程:A
    线程:B
    main线程
    

    它是如何做到的线程A优先于线程B,线程B优先于主线程的呢?

    为了说明这点,就要查看Thread.join的源码了:

         /** 等待该线程终止
         * Waits for this thread to die.
         * 调用此方法的行为方式与调用完全相同join (0)
         * <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);
    }
    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;
                }
            }
    }
    public final native void wait(long timeout) throws InterruptedException;
    

    ​ 结合上面的实例,先看主线程和线程B,在主线程中执行“thread2.join();”,也就相当进入到join方法体中,它会获取到当前实例的锁,也就是线程B对象的锁,然后判断线程是否存活后执行“ wait(0);”,执行该方法时,主线程会释放锁,进入到阻塞状态(也即进入到了该锁的WaitSet中)。为什么说是主线程进入到阻塞状态,而不是线程B进入到阻塞状态呢?

    为了解答这个问题,设计如下的实例:

    class ThreadTest extends Thread{
        public synchronized void method1(){
            System.out.println("hello world");
            System.out.println(Thread.currentThread().getName());
        }
    }
    public class Demo51 {
        public static void main(String[] args) {
            ThreadTest threadTest = new ThreadTest();
            threadTest.start();
            threadTest.method1();
        }
    }
    

    输出结果:

    hello world
    main
    

    ​ 可以看到在执行“threadTest.method1()”时,线程“threadTest”中输出的当前线程是主线程,而不是“Thread-0”,这是因为它是“ threadTest.method1()”是由主线程所调用的。

    ​ 再回到上面的问题中,调用“thread2.join();”导致主线程被阻塞,紧接着线程B开始执行,线程B执行完毕后开始执行主线程,而此时主线程还在阻塞状态(即还在该线程锁的waitset中),那么它是如何实现唤醒主线程,使得它能够接着执行的呢?实际上这是因为每个线程在退出时,会执行notifyAll唤醒所有阻塞在该实例锁上的线程。

    为了更为方便的说明这个问题,设计如下的实例,创建一个线程类,定义“method1”方法和“run”方法,然后在主线程中调用它

    class ThreadTest extends Thread{
    
        public synchronized void method1() throws InterruptedException {
            System.out.println("hello world");
            System.out.println(Thread.currentThread().getName());
            wait();
            System.out.println("==================");
        }
    
        @Override
        public void run() {
            try {
                Thread.sleep(10_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public class Demo51 {
        public static void main(String[] args) throws InterruptedException {
            ThreadTest threadTest = new ThreadTest();
            threadTest.start();
            threadTest.method1();
            System.out.println("===Exit===");
        }
    }
    

    运行结果:

    hello world
    main
    ==================
    ===Exit===
    

    ​ 来分析一下程序的执行,“ThreadTest threadTest = new ThreadTest()”创建线程对象,“threadTest.start();”启动这个线程,“threadTest.method1();”执行method1方法,关注点就在这里,在执行method1方法的时候,主线程会尝试获取“threadTest ”对象的锁,成功后进入到方法体,然后进入到阻塞状态,而threadTest 在启动后执行run方法中的内容,然后睡眠10秒钟,在随眠结束后执行完毕,threadTest 退出执行状态,在退出时,执行notify_all方法唤醒阻塞在threadTest 锁上的主线程。

    // 位于/hotspot/src/share/vm/runtime/thread.cpp中

    void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
    // ...
    // Notify waiters on thread object. This has to be done after exit() is called
    // on the thread (if the thread is the last thread in a daemon ThreadGroup the
    // group should have the destroyed bit set before waiters are notified).
    // 有一个贼不起眼的一行代码,就是这行
    ensure_join(this);
    // ...
    }
    
    static void ensure_join(JavaThread* thread) {
    // We do not need to grap the Threads_lock, since we are operating on ourself.
    Handle threadObj(thread, thread->threadObj());
    assert(threadObj.not_null(), "java thread object must exist");
    ObjectLocker lock(threadObj, thread);
    // Ignore pending exception (ThreadDeath), since we are exiting anyway
    thread->clear_pending_exception();
    // Thread is exiting. So set thread_status field in  java.lang.Thread class to TERMINATED.
    java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
    // Clear the native thread instance - this makes isAlive return false and allows the join()
    // to complete once we've done the notify_all below
    java_lang_Thread::set_thread(threadObj(), NULL);
    
    // 同志们看到了没,别的不用看,就看这一句
    // thread就是当前线程,是啥?就是刚才例子中说的threadA线程啊。
    lock.notify_all(thread);
    
    // Ignore pending exception (ThreadDeath), since we are exiting anyway
    thread->clear_pending_exception();
    }
    

    ————————————————
    版权声明:本文为CSDN博主「Mlib」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/u010983881/article/details/80257703

    ​ 同理在Thread.join中,也是如此。如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回;它的底层实现就是线程A进入到了thread对象的waitset中了,当thread的线程执行完毕后,在线程退出操作中,会自动唤醒阻塞在thread对象上的线程A,这样线程A也就能够继续执行了,表现为线程A等待thread执行完毕后,才接着执行。

    参考链接:Java Thread的join() 原理

    【Java】Thread类中的join()方法原理

    (四)Thread.join的作用和原理

  • 相关阅读:
    js伪数组转数组内部实现
    Vuex核心部分学习参考地址
    vue中让异步代码变成同步的写法
    node.js中文件操作路径和模板标识路径问题
    如果不想安装cnpm又想使用淘宝的服务器来下载,怎么做?
    npm常用命令
    node中模块加载机制
    通过nodejs,简单模拟客户端和服务端进行通信
    vue中非父子组件的传值
    图论1-2
  • 原文地址:https://www.cnblogs.com/cosmos-wong/p/12345299.html
Copyright © 2011-2022 走看看