【多线程: join 方法详解】
01.为什么需要 join
join的理解:
join的目的是为了把调用join的线程“插队”到了当前线程,并且 调用join的线程一定会把此线程运行结束。
补充两个概念
同步:需要等待结果返回,才能继续运行就是同步
异步:不需要等待结果返回,就能继续运行就是异步
02.join的使用
分析这段代码 说明r的值
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test10")
public class Test10 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("结束");
r = 10;
},"t1");
t1.start();
log.debug("结果为:{}", r);
log.debug("结束");
}
}
结果
14:50:09.053 c.Test10 [main] - 开始
14:50:09.096 c.Test10 [t1] - 开始
14:50:09.095 c.Test10 [main] - 结果为:0
14:50:09.097 c.Test10 [main] - 结束
14:50:09.098 c.Test10 [t1] - 结束
解释
因为此时为异步,且t1线程在给r赋值前 sleep了1秒,导致主线程先运行了==log.debug("结果为:{}", r)==这个语句 此时这个r还没有被赋值故结果为0
给t1线程加入join后
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test10")
public class Test10 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("结束");
r = 10;
},"t1");
t1.start();
t1.join();
log.debug("结果为:{}", r);
log.debug("结束");
}
}
结果
14:52:21.241 c.Test10 [main] - 开始
14:52:21.277 c.Test10 [t1] - 开始
14:52:21.279 c.Test10 [t1] - 结束
14:52:21.279 c.Test10 [main] - 结果为:10
14:52:21.280 c.Test10 [main] - 结束
解释
因为此时是同步,t1.join()后,把t1线程加入到了主线程,并且把t1执行完成后才会继续执行主线程,所以此时r已经赋值,所以此时r=10
03.一个例子
分析下面这段代码花费的时间
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.TestJoin")
public class TestJoin {
static int r = 0;
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test();
}
private static void test() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
r1 = 10;
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
r2 = 20;
});
t1.start();
t2.start();
long start = System.currentTimeMillis();
log.debug("join begin");
t1.join();
log.debug("t1 join end");
t2.join();
log.debug("t2 join end");
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
}
结果
15:23:18.025 c.TestJoin [main] - join begin
15:23:19.025 c.TestJoin [main] - t1 join end
15:23:20.025 c.TestJoin [main] - t2 join end
15:23:20.025 c.TestJoin [main] - r1: 10 r2: 20 cost: 2002
解释
因为t1线程先同步到主线程 但是t1线程sleep了1s 在这个过程中 程序在t2线程运行1s,之后继续运行t1线程,t1线程结束后 程序运行t2线程 t2线程同步到主线程 因为t2线程之前已经运行过1s 又因为t2线程sleep 2s 所以还需要1s,故总共需要时间为2s
若把t1 t2互换结果会有改变吗?
分析
结果是不会变的 还是2s ,因为 t2线程先同步到了主线程 t2线程sleep了2s 这期间 运行了t1线程 因为t1线程只是sleep了1s 故 t1线程运行结束 当2s后 切换到t1线程 t1线程运行结束,故总共还是2s
画图分析
t1在t2前的情况
再思考一个问题
因为我的电脑是多核cpu的,结果是2s,那么单核cpu结果还一样吗?
提出这个问题的原因是 会有人认为之所以 在t1线程睡眠期间 可以运行t2线程是因为另一个cpu运行了t2线程,那么按照这种思想 单核cpu在t1睡眠期间 是不会运行t2线程的,这样最后的时间就是3s
在单核服务器上查看运行结果
可以看到结果依旧是2s 说明上述推断是错误的,无论是单核还是多核 sleep过程中 空闲cpu都会执行其他线程