join 底层是 以线程为锁的 wait 应用,以等待 该线程运行结束,继续运行
该锁线程运行结束后 会唤醒 以该线程为锁的 其他线程(.start() 方法中 含有notify相关代码或者说具有相关的功能)
背景
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("开始");
sleep(1);
log.debug("结束");
r = 10;
});
t1.start();
log.debug("结果为:{}", r);
log.debug("结束");
}
分析
因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
而主线程一开始就要打印 r 的结果,所以只能打印出 r=0
解决方法
用 sleep 行不行?为什么? —时间不好把控
使用
用 t1.join,加在 t1.start() 之后即可
当前线程等待 t1 运行结束
作用
实现进程同步
- 需要等待结果返回,才能继续运行就是同步
- 不需要等待结果返回,就能继续运行就是异步
原理
将线程作为锁,调用者轮询检查该线程 alive 状态
t1.join();
等价于
synchronized (t1) {
// 调用者线程进入 t1 的 waitSet 等待, 直到 t1 运行结束
while (t1.isAlive()) {
t1.wait(0);//wait(0)表示一直休眠。和wait一样
}
}
注意到此处代码并没有使用 notify唤醒调用者,翻开join的源码也是如此,核心代码和上面等价代码的一样。
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 中 wait 似乎 没有使用 notify唤醒调用者,那为什么作为锁的线程结束后,调用者会被唤醒然后继续运行呢?
因此有理由怀疑 .start() 方法中 可能含有notify相关代码
然而翻看 .start() 源码,也没有发现noify的痕迹,唯一一个和线程运行关联比较大的代码是 start0();
这是一个本地方法,查阅资料得知 start0() 中调用了run方法,到由于语言问题 并不知道 start0()是否有 notify类似的操作。
start() 源码
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
但通过实验可以很确定,锁对象为线程(简称锁线程)时,锁线程运行结束后确实进行了notify操作。
下面进行如下实验证明
锁对象为线程
模拟join操作
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
System.out.println("thread睡眠");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread lock = thread;
thread.start();
//锁对象为线程模拟join操作
synchronized (lock){
System.out.println("等待thread睡眠结束");
lock.wait(0);
}
//理论上如果没有notify,主线程不会输出以下文字
System.out.println("未通过notify唤醒");
}
}
理论上如果没有notify,主线程不会输出 "未通过notify唤醒" ,然而
结果
锁对象为object
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
System.out.println("thread睡眠");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Object lock = new Object();
thread.start();
//锁对象为线程模拟join操作
synchronized (lock){
System.out.println("等待thread睡眠结束");
lock.wait(0);
}
//理论上如果没有notify,主线程不会输出以下文字
System.out.println("未通过notify唤醒");
}
}
可以看到使用Object对象作为锁的话,主线程不会被notify
结果
总结
join 底层是 以线程为锁的 wait 应用,以等待 该线程运行结束,继续运行
该锁线程运行结束后 会唤醒 以该线程为锁的 其他线程(.start() 方法中 含有notify相关代码或者说具有相关的功能)