Java线程中的join()方法
对于Java中的join()方法的描述,我们首先来看下源码当中的解释
从源码当中的描述,我们只能知道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
可以看到,结果是三个线程都在执行自己的方法,各自线程互不干扰。
image.png那么在此基础上,在调用tA.start()之后,再调用join(),再看运行结果:
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