Java线程中的join()方法

2023-04-12  本文已影响0人  Bfmall

对于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

上一篇下一篇

猜你喜欢

热点阅读