计算机为了提升CPU使用效率和交互性而引入了并发机制,任务的执行也抽象成了线程,并发机制让一个CPU能够轮流执行多个线程,从宏观上看多个线程就像是同时执行一样。并发使得线程的执行顺序不容易控制,而实际工程中很多场景都会涉及某个线程需要依赖另外一个或几个线程的执行结果,这就要被依赖的线程需要先执行完,这时就需要join操作。比如下面的场景,假如要计算A B的结果且A和B的计算都比较耗时,那么我们将B的计算分给另外一个线程,而线程一则负责A的计算。如果线程一先执行完则它要等待线程二,直到线程二计算出B的结果后线程一才继续往下执行,去计算A B。
01
Join 操作
从上面的介绍看来join操作类似于前面讲解的线程通知等待机制,的确是这样,join操作为我们提供了等待通知机制。某个线程可以通过调用join操作来等待另外一个线程的执行,直到另外一个线程执行完毕。我们来看下面的例子,线程t1首先创建了线程t2并启动该线程,接着线程t1继续创建线程t3,然后线程t1调用t2.join()和t3.join()进入等待状态。自此线程t1进入等待状态,而线程t2和线程t3一直执行,到它们俩都执行完毕后线程t1才会继续往下执行。
02
Join 案例
下面是一个简单的例子,主线程创建了线程t2并启动它,t2中通过睡眠三秒来模拟耗时计算,主线程中调用了t2.join()表示要等到t2执行完毕后才往下执行,也就是三秒后主线程才输出“got t2's result.”。
03
Join 的中断机制
从上一个例子可以看到主线程调用t2.join()后会一直处于等待状态,假如t2一直不执行完则主线程会一直等待下去。然而join操作是支持中断的,可以通过中断来解除join的阻塞。该例子中t2启动后会睡眠60秒,随后t3启动后主线程就进入等待状态。t3在睡眠三秒后就将主线程的中断标示设置为true,即进行中断操作。主线程解除阻塞,并输出“t3 has interrupted main thread.”。
04
Join 的超时机制
join操作默认会无限等待,也就是说不管另一个线程执行多久都将等待其运行完。但如果我们希望等待的时间是有期限的话则可以传入超时时间,一旦等待超过该指定时间则会解除阻塞。下面的例子中,与前面不同的地方在于其调用了t2.join(3000),也就是join的超时为3秒。t2会睡眠60秒,但主线程只会等待3秒就解除阻塞,然后输出“join timeout.”。
05
Join 的实现原理
最后我们来看join操作的实现原理,对应的核心源码为java.lang.Thread类中,不带参数的join方法实际上间接调用了join(0),所以主要逻辑在join(long millis)方法中。如果传入的超时时间为负数则会抛出非法参数异常,如果超时时间为0则调用wait(0)方法,该方法会使当前线程一直等待,直到其它线程进行了notify通知。也就是说JVM会负责在线程退出前去进行通知操作,从而让join解除等待状态。如果超时时间大于0则计算最长的等待时间,然后调用wait(delay)使线程进入等待状态,传入的参数使得在等待超时后能解除等待状态。关于wait和notify的模式和机制,先前有针对源码、案例以及实现原理分享过,可以查看 Java并发编程:多线程如何实现阻塞与唤醒
- END -