美文网首页
Java线程中的join()方法

Java线程中的join()方法

作者: Bfmall | 来源:发表于2023-04-12 14:55 被阅读0次

    对于Java中的join()方法的描述,我们首先来看下源码当中的解释

    image.png

    从源码当中的描述,我们只能知道join方法的作用是让线程陷入等待。其中可以传递以毫秒为单位的等待时间参数,如果传递参数为0,则线程会一直等待。

    其实对于join方法,网上有很多解释,大都是只说如何使用,并没有对join当中的实现进行分析。
    因此,在此结合网上的各种说法,对线程中的join方法进行源码分析,同时也记录自己的学习过程。

    首先先来了解join方法如何使用,以及它的作用。
    为了更有对比性的展示,首先来个简单并正常使用(不使用join)情况的多线程程序:

    public class Main {
        public static void main(String[] args) throws InterruptedException {
    
            Counter counter = new Counter();
    
            Thread tA = new Thread(new Runnable() {
                @Override
                public void run() {
                    counter.printA();
                }
            });
    
            Thread tB = new Thread(new Runnable() {
                @Override
                public void run() {
                    counter.printB();
                }
            });
    
            Thread tC = new Thread(new Runnable() {
                @Override
                public void run() {
                    counter.printC();
                }
            });
    
            tA.start();
            tB.start();
            tC.start();
        }
    
        static class Counter {
            
            public void printA() {
                try {
                    Thread.currentThread().sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 5; i++) {
                    System.out.println("A");
                }
            }
    
            public void printB() {
                try {
                    Thread.currentThread().sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 5; i++) {
                    System.out.println("B");
                }
            }
    
            public void printC() {
                try {
                    Thread.currentThread().sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 5; i++) {
                    System.out.println("C");
                }
            }
        }
    }
    

    这个程序只是创建了三个线程,然后进行调用。程序结果如下:


    image.png

    可以看到,结果是三个线程都在执行自己的方法,各自线程互不干扰。

    那么在此基础上,在调用tA.start()之后,再调用join(),再看运行结果:

    image.png
    image.png

    可以看到,tA.start()当中的内容都执行完后,才轮到后面的tB和tC线程执行。
    这个结果便是网上最常说的 “t.join()方法会使所有线程都暂停并等待t的执行完毕后再执行”

    但如果看过源码就知道,这种说法是十分片面的
    请看join的源码:

        /**
         * 解释:最多等待{@code millis}毫秒,以使该线程死亡。{@code 0} 超时意味着永远等待
         * Waits at most {@code millis} milliseconds for this thread to
         * die. A timeout of {@code 0} means to wait forever.
         */
        public final void join(long millis)
        throws InterruptedException {
            synchronized(lock) {
            long base = System.currentTimeMillis();
            long now = 0;
    
            if (millis < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
    
            if (millis == 0) {
                while (isAlive()) {
                    lock.wait(0);
                }
            } else {
                while (isAlive()) {
                    long delay = millis - now;
                    if (delay <= 0) {
                        break;
                    }
                    lock.wait(delay);
                    now = System.currentTimeMillis() - base;
                }
            }
            }
        }
    

    通过源码了解,join之所以可以实现线程等待是因为调用wait方法。

    那么对于线程不够了解的朋友就可能会问:在本文的示例中是tA.join(),而join中又调用了wait。那么不是让tA执行的线程陷入等待了吗,那不就是和网上的说法“让tA线程执行完,再执行其他线程”完全不一致了?

    如果你有这样的疑问的话,说明你对wait方法也不够了解。
    来看下wait方法的源码注释解释:


    image.png

    从wait方法的方法注释可以看到,wait方法会让当前线程陷入等待。注意,是当前线程!

    那么我们再回头看下示例,在执行tA.join()这句代码的时候,当前线程是谁?
    没错,是main主线程,而不是tA执行的线程。
    tA执行的线程和main线程同时都在执行,而调用执行A.join()这句代码确确实实是在main主线程当中执行的。

    因此在tA.join()当中的wait(0)方法是让main线程陷入了无尽的等待中。正是因为如此,在tA.join()之前的代码都会正常从上往下执行,而在tA.join()之后的代码都随着main线程陷入等待而无法继续执行。这样便达到了网上说的 “t.join()方法会使所有线程都暂停并等待t的执行完毕后再执行”。

    到此,关于join的讲解尚未结束。之前有说到,join方法中调用wait(0)让当前线程陷入无尽的等待。那么有wait等待就会有相应的notify或者notifyAll唤醒,那么唤醒的地方又在哪里?

    这个问题我也跟踪过join的方法,但始终发现不了在哪里唤醒等待的线程。所以在此引用网上的结论:join源码中,只会调用wait方法,并没有在结束时调用notify,这是因为线程在die的时候会自动调用自身的notifyAll方法,来释放所有因为该锁陷入等待的资源和锁。

    #thread.cpp
    //调用join后,是在JVM当中调用该方法自动唤醒线程
    void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
      ...
      // 这个方法入口
      ensure_join(this);
      ...
    }
    
    static void ensure_join(JavaThread* thread) {
      ...
      ObjectLocker lock(threadObj, thread);
      ...
      //唤醒等待在thread对象上的线程
      lock.notify_all(thread);
      ...
    }
    
    #ObjectSynchronizer.hpp
    void notify_all(TRAPS)      { 
      ObjectSynchronizer::notifyall(_obj, CHECK); 
    }
    

    参考:https://blog.csdn.net/weixin_41083377/article/details/114598071

    相关文章

      网友评论

          本文标题:Java线程中的join()方法

          本文链接:https://www.haomeiwen.com/subject/otcoddtx.html