参考资料
join方法
1 | csharp复制代码join重载方法 |
功能演示
1 | java复制代码public class JoinDemo implements Runnable{ |
以上将t.join();注释掉,执行的一种可能结果如下:
1 | arduino复制代码main thread start... |
但是把注释去掉,结果如下:
1 | arduino复制代码main thread start... |
这是一个非常简单的demo,效果是显而易见的。当main线程去调用t.join()是,会将自己当前线程阻塞,等到t线程执行完成到达完结状态,main线程才可以继续执行。
我们看一下join()设置超时时间的方法:
1 | java复制代码public class JoinDemo implements Runnable{ |
执行效果:
1 | arduino复制代码main thread start... |
上面的执行结果可以看到,子线程设置了4s的超时时间,但是主线程在1秒超时后,并没有等待子线程执行完毕,就被唤醒执行后续操作了;这样的预期是否符合你的预期呢?
下面我们按照join的源码去分析吧!
join方法原理
下面是join的原理图
join()源码
首先会调用join(0)方法,其实是join的重载方法;
1 | csharp复制代码public final void join() throws InterruptedException { |
下面是join的核心实现:
1 | arduino复制代码public final synchronized void join(long millis) |
下面是isAlive方法的源码
1 | java复制代码public final native boolean isAlive(); |
这是一个本地方法,作用是判断当前的线程是否处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。
- 这里有一个点要注意,join为什么阻塞的是主线程,而不是子线程呢?
- 不理解的原因是阻塞主线程的方法是放在previousThread这个实例作用,让大家误以为应该阻塞previousThread线程。实际上主线程会持有previousThread这个对象的锁,然后调用wait方法去阻塞,而这个方法的调用者是在主线程中的。所以造成主线程阻塞。
- 其实join()方法的核心在于wait(),在主线程中调用t.join()相当于在main方法中添加 new JoinDemo().wait();是一样的效果;在这里只不过是wait方法写在了子线程的方法中。
- 再次重申一遍,join方法的作用是在主线程阻塞,等在子线程执行完之后,由子线程唤醒主线程,再继续执行主线程调用t.join()方法之后的逻辑。
那么主线程是在什么情况下知道要继续执行呢?就是上面说的,主线程其实是由join的子线程在执行完成之后调用的notifyAll()方法,来唤醒等待的线程。怎么证明呢?
其实大家可以去翻看JVM的源码实现,Thread.cpp文件中,有一段代码:
1 | arduino复制代码void JavaThread::exit(bool destroy_vm, ExitType exit_type) { |
其中调用ensure_join方法
1 | scss复制代码static void ensure_join(JavaThread* thread) { |
在JVM的代码中,线程执行结束的最终调用了lock.notify_all(thread)方法来唤醒所有处于等到的线程
使用场景
- 比如我们使用Callable执行异步任务,需要在主线程处理任务的返回值时,可以调用join方法;
- 还有一些场景希望线程之间顺序执行的;
join()方法与sleep()的比较
我们先说一下sleep方法:
- 让当前线程休眠指定时间。
- 休眠时间的准确性依赖于系统时钟和CPU调度机制。
- 不释放已获取的锁资源,如果sleep方法在同步上下文中调用,那么其他线程是无法进- 入到当前同步块或者同步方法中的。
- 可通过调用interrupt()方法来唤醒休眠线程。
- sleep是静态方法,可以在任何地方调用
相比与sleep方法
sleep是静态方法,而且sleep的线程不是放锁资源,而join方法是对象方法,并且在等待的过程中会释放掉对象锁;
关于join方法会释放对象锁,那到底是释放的那个对象的锁呢,可以参照 关于join() 是否会释放锁的一些思考
一些关于join的面试题
山脚太拥挤 我们更高处见。
本文转载自: 掘金