1.概述
join()是Thread类中的一个方法,它的作用是将当前线程挂起,等待其他线程结束后再执行当前线程,即当前线程等待另一个调用join()方法的线程执行结束后再往下执行。通常用于在main主线程内,等待其它调用join()方法的线程执行结束再继续执行main主线程。本文将探索join方法的使用方式和使用原理。
2.join方法使用
2.1 join()示意图
2.1.1线程运行示意图
上述案例示意图中,主线程A入栈运行,运行时创建子线程B和线程C执行任务,B线程或C线程调用join方法,阻塞当前线程A,B或C线程执行完成,A线程才会继续执行。
2.1.2案例代码
public class ThreadB extends Thread {
public void run() {
System.out.println("线程:" + Thread.currentThread().getName() + "休眠:10s");
try {
TimeUnit.SECONDS.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + "休眠结束");
}
}
public class ThreadC extends Thread {
public void run() {
System.out.println("线程:" + Thread.currentThread().getName() + "休眠:1s");
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + "休眠结束");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程线程A开始运行。。。");
ThreadB threadB = new ThreadB();
threadB.setName("ThreadB");
threadB.start();
ThreadC threadC = new ThreadC();
threadC.setName("ThreadC");
threadC.start();
threadB.join();
System.out.println("主线程线程A结束运行");
}
}
运行结果如下:
方法join的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x被销毁后再继续执行线程z后面的代码。
2.2 join方法原理
先看一下join()方法的源码,源码基于JDK 1.8,具体如下:
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) {
//判断线程是否存活,若为true,调用wait()方法阻塞
while (isAlive()) {
wait(0);
}
} else {
//阻塞指定时间
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
由上述源码可知,join方法的底层实际上还是调用了wait()方法,通过wait()方法实现线程阻塞等待,那么这里有个问题,阻塞完成后,如何通知呢?我们知道调用wait阻塞的线程,需要notify()或者notifyAll来进行唤醒,这里并没有显式调用这两个方法,那是如何实现的呢?这里涉及一个知识:
在java中,Thread类线程执行完run()方法后,会自动执行notifyAll()方法。
具体的实现在HotSpot的源码中,有个ensure_join方法,大家感兴趣可查看。
2.3 注意事项
2.3.1 join方法被interrupt会异常
测试代码如下:
public class ThreadA extends Thread {
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(i);
String s = stringBuffer.toString();
}
}
}
public class ThreadB extends Thread {
@SneakyThrows
@Override
public void run() {
ThreadA threadA = new ThreadA();
threadA.start();
threadA.setName("ThreadA");
threadA.join();
System.out.println(Thread.currentThread().getName() + "运行完成!");
}
}
public class ThreadC extends Thread {
private ThreadB threadB;
public ThreadC(ThreadB threadB) {
this.threadB = threadB;
}
@Override
public void run() {
threadB.interrupt();
System.out.println(Thread.currentThread().getName() + "运行结束!");
}
}
public class Test {
public static void main(String[] args) {
ThreadB threadB = new ThreadB();
threadB.start();
threadB.setName("ThreadB");
try {
TimeUnit.SECONDS.sleep(1);
}catch (Exception e) {
e.printStackTrace();
}
ThreadC threadC = new ThreadC(threadB);
threadC.setName("ThreadC");
threadC.start();
System.out.println(Thread.currentThread().getName()+"运行结束");
}
}
运行结果如下:
上述代码中,ThreadB 中创建了一个ThreadA执行,并让threadA.join(),又让线程ThreadC调用线程ThreadB的interrupt()方法来打断进程。由运行结果可知,方法join()与interrupt()相遇,会出现异常,而上述进程又未终止,说明ThreadA还在运行。
2.3.2 方法join(long)与sleep(long)的区别
由上述join方法的源码可知,在执行完wait(long)方法后,当前线程的锁被释放,那么其它线程就可以调用此线程中的同步方法。sleep(long)方法却不释放锁,也就意味着当线程调用sleep方法时,线程会被阻塞,其它线程只能等待获取锁。
测试代码如下:
public class ThreadA extends Thread {
private ThreadB threadB;
public ThreadA(ThreadB threadB) {
this.threadB = threadB;
}
@Override
public void run() {
try {
synchronized (threadB) {
threadB.start();
Thread.sleep(6000L);
System.out.println(Thread.currentThread().getName() + "结束运行!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ThreadB extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName() + "开始运行:" + System.currentTimeMillis());
try {
Thread.sleep(5000L);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束:" + System.currentTimeMillis());
}
public synchronized void print() {
System.out.println(Thread.currentThread().getName() + "打印了时间:" + System.currentTimeMillis());
}
}
public class ThreadC extends Thread {
private ThreadB threadB;
public ThreadC(ThreadB threadB) {
this.threadB = threadB;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始运行!");
threadB.print();
System.out.println(Thread.currentThread().getName() + "运行完成!");
}
}
public class Test {
public static void main(String[] args) {
ThreadB threadB = new ThreadB();
threadB.setName("ThreadB");
ThreadA threadA = new ThreadA(threadB);
threadA.start();
threadA.setName("ThreadA");
try {
Thread.sleep(1000L);
ThreadC threadC = new ThreadC(threadB);
threadC.setName("ThreadC");
threadC.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果如下:
由上述代码可知,线程ThreadA调用sleep方法一直持有线程ThreadB对象的锁,时间达到6s,所以线程ThreadC只能在ThreadA时间达到6s后释放线程ThreadB的锁时,才可以调用ThreaB的同步方法print()。由上述现象可知,Thread.sleep(long)不释放锁。
修改上述ThreadA中的代码,验证join()方法释放锁,具体代码如下:
public class ThreadA extends Thread {
private ThreadB threadB;
public ThreadA(ThreadB threadB) {
this.threadB = threadB;
}
@Override
public void run() {
try {
synchronized (threadB) {
threadB.start();
//Thread.sleep(6000L);
threadB.join();
System.out.println(Thread.currentThread().getName() + "结束运行!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果如下:
由上述运行结果可知,在线程ThreadA中调用ThreadB的join()方法,线程ThreadC在ThreadB未执行完成后,获取到了锁,执行了同步方法print(),说明join(long)方法会释放锁。
2.3.3 join()方法后的代码提前执行
案例代码如下:
public class ThreadA extends Thread {
private ThreadB threadB;
public ThreadA(ThreadB threadB) {
this.threadB = threadB;
}
@Override
public void run() {
try {
synchronized (threadB) {
System.out.println(Thread.currentThread().getName() + "开始运行!");
Thread.sleep(5000L);
System.out.println(Thread.currentThread().getName() + "结束运行!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ThreadB extends Thread {
public synchronized void run() {
System.out.println(Thread.currentThread().getName() + "开始运行:" + System.currentTimeMillis());
try {
Thread.sleep(5000L);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束:" + System.currentTimeMillis());
}
}
public static void main(String[] args) {
try {
ThreadB threadB = new ThreadB();
ThreadA threadAA = new ThreadA(threadB);
threadAA.start();
threadAA.setName("threadAA");
threadB.setName("threadB");
threadB.start();
threadB.join(2000);
System.out.println(Thread.currentThread().getName() + "结束运行!");
} catch (Exception e) {
e.printStackTrace();
}
}
运行结果如下:
出现上述情况的原因如下:
1.b.join(2000)方法先抢到threadB锁,然后将锁释放;
2.ThreadA抢到锁,打印:threadAA开始运行,并且sleep 5s;
3.ThreadA打印:threadAA运行结束,并释放锁;
4.此时join(2000)和ThreadB争抢锁,而join(2000)再次抢到锁,发现时间已过,释放锁后打印:main运行结束;
5.ThreadB抢到锁打印ThreadB开始运行;
6.5s后ThreadB打印:threadB运行结束。
2.3.4 Thread.currentThread().join()方法
若调用这个方法,当前线程将一直被阻塞,无法退出,开发中因谨慎使用。修改2.1.2节中测试代码为:
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程线程A开始运行。。。");
ThreadB threadB = new ThreadB();
threadB.setName("ThreadB");
threadB.start();
ThreadC threadC = new ThreadC();
threadC.setName("ThreadC");
threadC.start();
//threadB.join();
Thread.currentThread().join();
System.out.println("主线程线程A结束运行");
}
出现运行结果如下:
3.小结
1.join()方法主要使用场景是一个线程需要等待另线程的运行结果,需要阻塞当前线程等待;
2.join()和join(long)的主要区别是:join(long)阻塞指定时间;join()一直阻塞,直至线程被销毁;
3.join() 方法被interrupt()会抛出异常,join()方法使用后会释放锁,sleep(long)方法却不释放锁;
4.Thread.currentThread().join()方法会一直阻塞线程。
4.参考文献
1.https://www.jb51.net/article/216689.htm
2.《JAVA多线程编程核心技术》-高洪岩著