join/join(timeout)
方法可以通知让主线程等待,直到子线程结束后,主线程再继续执行,那么究竟是怎样实现的呢?
本章内容主要探究的问题:
(1). 从源码层面剖析join/join(timeout)
方法到底做了什么?
(2). 线程join
方法阻塞,那么什么时候唤醒?如何唤醒?
好了接下来一起分析…
join(timeout)
方法(图1-1),相当于join(0)
;在join(timeout)
中,是一个synchronized
修饰的方法,里面会判断传入的时间参数不能小于0,小于抛出参数异常;join()
方法调用进来的,会调用isAlive()
方法检查线程是否存活,存活则调用wait(0)
方法让其进入无限等待…join(timeout)
调用进来的,会根据传入的时间参数和当前时间进行计算,时间已到则退出,否则继续wait(timeout)
循环等待。isAlive()
方法是判断调用此join()
方法的线程是否存活(t1
线程),这里的wait()/wait(timeout)
方法是让主线程进行等待(mian
线程)。synchronized
锁是this
锁,也就是说谁来调用join()
方法,谁就是锁(这里锁就是t1
)。
(图1-1)
4. 好了,到了这里,也就是说join()
方法的本质是wait()
方法;当调用wait(timeout)
方法,timeout>0
则到了等待时间后自动唤醒,timeout=0
时,会无限等待,那么无线等待由谁来唤醒呢?wait
方法可以看我之前的博客线程方法wait/wait(timeout)源码分析)
}
花括号之后),会在jvm
层面调用exit()
方法进行释放锁。jvm
的JavaThread::exit()
方法中,会调用一个ensure_join()
方法(图1-2),在此方法中,创建了ObjectLocker
对象,实际上是进行了加锁(构造函数加锁);调用lock.notify_all()
方法进行挪动在waitSet
的线程节点;还并没有唤醒,也就是说…现在已经加了锁,在哪里释放锁呢?注意:(线程唤醒并不是notify
方法唤醒的,notify
方法只是挪动线程包装节点(ObjectWaiter
),真正唤醒是在出了锁之后唤醒,可以阅读我之前的文章线程方法notify/notifyAll源码分析)。
(图1-2)
=========================================================
在c++
语法中,有一种析构函数,析构函数与构造函数是相反的,构造方法用于开辟内存空间,创建对象;而析构函数用于资源释放;析构函数是c++自动调用的;也就是说,调用构造函数创建对象,使用完后会自动调用析构函数释放资源。
构造函数为ObjectLocker()
,析构函数为~ObjectLocker()
。(图1-3)
ObjectLocker
对象,实现了加锁,在方法结束时,会自动调用~ObjectLocker
析构函数,在析构函数中进行释放锁,唤醒线程。
(图1-3)
也就是说,在线程执行结束后,在jvm
的c++
层面为你(加锁(线程的内置锁),唤醒,释放锁)。
=========================================================
代码证明:
测试:有t1和t2两个线程,t1进入后打印 t1…start,然后休眠2s;t2线程持有t1的锁,也会打印 t2…start,休眠5s后挂起;
按照逻辑,t1只休眠2s,会先醒来,打印 t1…end 然后t1执行结束;
t2在5s后醒来,打印t1线程是否存活,发现t1线程仍然存活!然后wait挂起,最后居然打印出了 t2…end。
疑问:当t1执行完后,t2打印t1的线程状态,为何还是处于存活状态? t2线程中调用了wait方法等待,是如何被唤醒的呢?有兴趣可以把下面代码运行试一试
/**
* 测试线程join方法
*
* 测试:有t1和t2两个线程,t1进入后打印 t1...start,然后休眠2s;t2线程持有t1的锁,也会打印 t2...start,休眠5s后挂起;
* 按照逻辑,t1只休眠2s,会先醒来,打印 t1..end 然后t1执行结束;
* t2在5s后醒来,打印t1线程是否存活,发现t1线程仍然存活!然后wait挂起,最后居然打印出了 t2...end。
* 疑问:1.当t1执行完后,t2打印t1的线程状态,为何还是处于存活状态?
* 2.t2线程中调用了wait方法等待,是如何被唤醒的呢?
*
* join方法的本质,是判断join的线程是否存活,如果存活,那么当前线程将wait等待直至join的线程死亡; 实质上是调用wait方法
* 何时唤醒?当join的线程执行结束后,会在jvm的c++层面(加锁(t1的内置锁),唤醒,释放锁),其中调用了notifyAll方法
* 也就是在锁退出时,调用c++的exit方法,在exit中的ensure_join方法里,创建ObjectLocker对象进行加锁,然后~ObjectLocker析构函数里释放锁
*
* 解答:1. t1线程执行完后,t2线程休眠时,t1其实并没有真正执行结束,因为还需要进行(加锁,唤醒,释放锁),而现在t2线程拿着t1的锁,
* t1只有等待t2执行完后才能获取到锁,所以在t2的5s醒来之后,打印t1仍为存活状态。
* 2.在t1执行完之后,在jvm的c++层面进行(加锁,唤醒,释放锁),所以t2是在这里进行唤醒的。
*/
public class TestJoin {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "..start");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "..end");
}//线程执行结束后 会在jvm的c++层面会给你(加锁(t1的内置锁),唤醒,释放锁)
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (t1) {//使用t1锁时,当t1线程执行完毕后会被唤醒
System.out.println(Thread.currentThread().getName() + "..start");
try {
Thread.sleep(5000);
System.out.println(t1.isAlive());//睡眠5s,占用着t1的锁,所以t1无法真正执行完毕(因为线程结束后需要加锁,t1的内置锁),t1线程仍然存活
t1.wait();//执行wait方法后释放t1的锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "..end");
}
}
}, "t2");
t1.start();
t2.start();
}
}
总结:join/join(timeout)
方法本质上是调用的wait/wait(timeout)
方法。当线程结束后,会在jvm的c++层面给你(加锁,唤醒,释放锁)。
感谢各位的阅读,如有不正确的地方,欢迎评论指正!