JUC并发编程——Join方法分析:为什么主线程中调用子线程的Join方法会导致主线程阻塞,而不是子线程阻塞?(没写完,不想写了)

1、例子代码
public class JoinTest {
    
    public static void main(String[] args) throws Exception {
        String start = nowTime();
        System.out.println(start + "-->" + Thread.currentThread().getName() + " end!");
        
        Thread threadA = new Thread(new JoinThread(2), "A");
        Thread threadB = new Thread(new JoinThread(5), "B");
        Thread threadC = new Thread(new JoinThread(5), "C");
        
        threadA.start();
        threadB.start();
        threadC.start();
        
        threadA.join();
        
        String end = nowTime();
        System.out.println(end + "-->" + Thread.currentThread().getName() + " end!");
    }
    
    static class JoinThread implements Runnable {
        
        private int sleepTime;
        
        public JoinThread(int sleepTime) {
            this.sleepTime = sleepTime;
        }
        
        @Override
        public void run() {
            try {
                String start = nowTime();
                System.out.println(start + "-->" + Thread.currentThread().getName() + " run!");
                Thread.sleep(sleepTime * 1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            String end = nowTime();
            System.out.println(end + "-->" + Thread.currentThread().getName() + " end!");
        }
        
    }
    
    public static String nowTime()
    {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }
}
2、代码说明

上面给出的代码很简单,就一个实现了“Runnable”接口的静态内部类“JoinThread”和一个测试类“JoinTest”(用于创建多个线程并执行join方法)。

在“JoinThread”的方法中接收一个“sleepTime”参数来指定休眠时间,并在“JoinThread”的线程实例的run()方法中打印启动和结束时的时间。

并且,我们创建并指定了线程A的休眠时间为2S,线程B和线程C的休眠时间为5S(这个可以证明join方法并不会影响其他正在运行的线程,提一嘴,在文章中不会去分析,可以找其他文章看看)

3、代码运行结果

可以看到,主线程(main)启动后,会创建并启动A,B,C三个子线程,同时调用了子线程A的join方法导致主线程阻塞,直到2秒后子线程A休眠结束并唤醒了主线程(子线程A唤醒主线程的动作是在Native方法中执行的,后面会分析),主线程打印“main end”结束运行,然后3秒后子线程B和子线程C结束休眠,程序结束。

4、源码分析和断点调试
(1)调用ThreadA.join()会首先进入Thread.join()方法中,因为join方法是可以指定主线程等待时间的,直接调用join()方法会调用join(0)方法,即默认"一直等待,直到子线程A结束"

(2)在join()方法中,有三种情况,millis[ 即join(long millis)方法中我们指定的等待时间 ]小于0时,会抛出异常;而millis为0时,就用一个while循环判断子线程当前是否处于“活跃”状态,然后用wait(0)让主线程处于“等待”状态,直到子线程A结束后唤醒主线程,主线程才继续在控制台打印“main end!”;而millis不为0情况在本文中不做分析(其实只要你看完了这篇文章,自然而然地就会明白了,你再复制我的代码,自己亲手去调试一下,你会有更深地理解,才真正地把知识拿走了)。

(3)wait(0)方法分析

看了上面的第二步之后,大家应该会有个疑问:为什么子线程A的wait(0)方法会让主线程处于“等待”状态,而不是让子线程A处于“等待”状态???这wait(0)方法明明是子线程A的啊?

这里就不得不提一下wait(0)方法的功能:让我们当前正在所处于的线程转为“等待”状态。

这里肯定有人会懵(我也是一样),当前线程不是子线程A吗?但其实我们现在还是处于主线程中,我们可以理解为:只是在主线程运行过程中调用了一个普通对象:threadA的join()方法,我们还没有离开当前的主线程,可以从下面的debug中看出

Thread.currentThread()就是我们当前正在运行的线程,我们可以看到Thread.currentThread()="Thread[main, 5, main]",即我们当前所处于的线程就是主线程

this指的是我们当前在哪个对象里,我们在主线程中调用了threadA的join()方法,所以毫无疑问地,我们当前的对象就是:threadA

而下面这段代码中的while循环isAlive()方法

public final synchronized void join(long millis) throws InterruptedException {
  
  

                // 省略部分无关代码

        while (isAlive()) {
            wait(0);
        }

                // 省略部分无关代码

}

就是在判断子线程是否处于运行状态isAlive()/this.isAlive()都表明子线程A:threadA正在运行,所以进入while循环调用wait(0)方法,将主线程暂停!

(4)子线程A结束,主线程继续运行

从上面两张图可以看到,子线程A:threadA的状态isAlive()/this.isAlive()为false,说明子线程A已经结束了,所以主线程的状态就是true。

在断点调试情况下继续执行,主线程也打印了“main end”

注意!!!因为我是边断点调试边写文章,所以上面这张图中“A/B/C end”打印会比较诡异,结果请以之前的运行情况为准,不要弄混!!!

最后,经过我们分析,详细解释了为什么主线程调用子线程的join()方法会让主线程阻塞,而不是让子线程阻塞的原因。

猜你喜欢

转载自blog.csdn.net/qq_42924347/article/details/132869632
今日推荐