程序猿学社的GitHub,欢迎Star
https://github.com/ITfqyd/cxyxs
本文已记录到github,形成对应专题。
前言
学过数据库的社友,应该都使用过join,当然,本文讲的join,是线程的join方法。
结论
先给出结论,join会堵塞当然调用的线程,直到拿到结果后,才会继续执行当前调用的那个线程。
- 回想起,19年的一个大冬天,凌晨2点,高铁转火车,火车还晚点2小个小时,坐过火车的人,应该都知道,火车的人数是真的多,所以,我早早的开始排队,大约从第50个,变成100,200,300。join可以理解为,就是这些插队的人,我很急,急着回家。当然,插队的行为,真的十分的不文明。
场景
有个别社友就在问,这个学了以后,能否派上用处,对此,我只能说,这个社友还是蛮务实的,实际上,很多的东西,不一定需要派上用场,才去学习他,就是为了扩充我们的视野,把我们的基础打牢固。我一直坚信的一个理念,存在便有一定的道理。
- 有不少做过导出的朋友,可以发现,有时候大数据量导出很慢,又不少朋友在想办法看看能否缩短这个时间。这里我们就可以使用多线程导出,通过join获取结果,再把结果合并,例如一页10w条,你查下出来需要20s,你设计成10页,起10个线程,是不是1次只用查询1w条,是不是大大的节约了时间。
- 一般需要等子线程j的运行结果后,主线程才继续运行的场景,我们都可以使用join。
实战
不加join代码
package com.cxyxs.thread.nine;
/**
* Description:转发请注明来源 程序猿学社 - https://ithub.blog.csdn.net/
* Author: 程序猿学社
* Date: 2020/2/28 10:21
* Modified By:
*/
public class JoinThread implements Runnable{
@Override
public void run() {
System.out.println("子线程在运行中!");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程已运行完!");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new JoinThread());
thread.start();
//thread.join();
Thread.sleep(100);
System.out.println("main方法已运行完!");
}
}
不适用join大家猜猜,输出到控制台的是什么结果?
- sleep休眠100毫秒,也是为了保证子线程先运行。不然,这个结果很难确定谁先,谁后。
- 首先main方法运行,main方法休眠100ms,子线程就抢到时间片,打印一句话,子线程休眠1秒,main方法抢到时间片,继续运行。
增加join代码
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new JoinThread());
thread.start();
thread.join();
Thread.sleep(100);
System.out.println("main方法已运行完!");
}
增加一个join,大家觉得,他的输出结果应该是什么(JoinThread类的代码省略点)?
- 我们可以发现,先运行完后子线程的所有业务后,才开始运行main后面的逻辑代码。所以join,我们可以理解为插队加塞。在main线程里面调用,就会堵塞main线程。一直到返回结果后,才继续运行main线程。
看到这里,又有社友提出一个疑问,会不会堵塞其他的子线程。我们再增加一个子线程试试。
增加join代码,模拟两个子线程
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("t1线程正在工作中");
});
Thread thread = new Thread(new JoinThread());
thread.start();
//这句代码很关键
thread.join();
t1.start();
Thread.sleep(100);
System.out.println("main方法已运行完!");
}
出现这种结果后,很多社友纷纷给我说,社长,脸痛不,这打脸也太快了把。
为什么会出现这种结果?
- thread线程启动后,就直接调用join方法,很明显会堵塞main线程,t1都没有启动,怎么去cpu中抢时间片,是不是很多社友,都觉得社长在狡辩。没关系,我们把join的位置变动一下,奇迹的事情,就会发生了,各位社友,睁大眼睛,好好看。
模拟两个子线程,移动join位置
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("t1线程正在工作中");
});
Thread thread = new Thread(new JoinThread());
thread.start();
t1.start();
//这句代码很关键
thread.join();
Thread.sleep(100);
System.out.println("main方法已运行完!");
}
- 通过上图的测试结果,我们可以发现,堵塞的只是当前线程,也就是我们的main线程(调用方),依次,我们可以下这个结论,谁调用谁堵塞,不会影响其他的线程正常的运行。是不会影响其他的子线程正常的运行的。这也是不少人存在的一个误区。
源码分析
通过上面的demo,我们对join方法的使用,有了一个大致的认识。但是,他底层的代码怎么一回事,话不多说,上源码。
我们大部分的时候,使用的都是不带参数的调用join
public final void join() throws InterruptedException {
join(0);
}
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) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
- 直接调用join方法,不指定参数,默认就是0,0标识没有设置超时时间会一直等待。
- 大于0,会判断线程,在规定的时间内,是否执行完,没有执行完,也不会等待。
- isAlive方法,判断线程是否活着。
- join方法实际上就是调用wait方法实现线程的堵塞。知道调用notify才会被唤醒。
- join函数用了synchronized关键字,表示线程安全。
很多的社友,很困惑,在mian方法中调用另外一个线程的join方法,为什么,main线程会进入等待状态,直到另外一个线程运行完后,才开始运行。
通过查看源码,我们可以知道,调用join,实际上就是调用wait,我们都知道wait是与synchronized成对存在的。也就说让持有这个锁的线程进入等待队列中,那是谁进入等待队列?
查看join的源码,我们可以发现他就是一个同步代码块,main线程调用join。所有main线程会进入等待队列中 。
main线程为什么等待一段时间后,又可以继续运行?
这是因为,再main线程中,调用的另外一个线程,结束以后,会调用notifyAll方法,从而我们的main线程,才能继续运行。