前言
本文隶属于专栏《100个问题搞定Java并发》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!
本专栏目录结构和参考文献请见100个问题搞定Java并发
正文
为什么需要 join?
在很多情况下,线程之间的协作和人与人之间的协作非常类似。
一种非常常见的合作方式就是分工合作。
以我们非常熟悉的软件开发为例,在一个项目进行时,总是应该有几位号称是“需求分析师”的同事,先对系统的需求和功能点进行整理和总结,以书面形式给出份需求说明或者类似的参考文档,然后,软件设计师、研发工程师オ会一拥而上,进行软件开发。
如果缺少需求分析师的工作输出,那么软件研发的难度可能会比较大。
因此,作为名软件研发人员,总是喜欢等待需求分析师完成他应该完成的任务后,才愿意投身工作。
简单地说,就是研发人员需要等待需求分析师完成他的工作,然后才能进行研发。
将这个关系对应到多线程应用中,很多时候,一个线程的输入可能非常依赖于另外个或者多个线程的输出,此时,这个线程就需要等待依赖线程执行完毕,才能继续执行。
JDK 提供了 join() 操作来实现这个功能。
join 是什么?
如下所示,显示了两个 join ()方法:
public final void join()throws InterruptedException
public final synchronized void join(long millis)throws InterruptedException
第一个 join ()方法表示无限等待,它会一直阻塞当前线程,直到目标线程执行完毕。
第二个方法给出了一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为“等不及了”,而继续往下执行。
英文 join 的翻译,通常是加入的意思。
这个意思在这里也非常贴切。
因为一个线程要加入另外一个线程,最好的方法就是等着它一起走。
join() 方法的本质是让调用线程 wait() 方法在当前线程对象实例上。(详情请见下面的源码解读)
yield 是什么?
另外一个比较有趣的方法是 Thread.yield ,它的定义如下:
public static native void yield();
这是一个静态方法,一旦执行,它会使当前线程让出 CPU 。
但要注意,让出 CPU 并不表示当前线程不执行了。
当前线程在让出 CPU 后,还会进行 CPU 资源的争夺,但是是否能够再次被分配到就不一定了。
因此,对 Thread.yield 方法的调用就好像是在说:“我已经完成了一些最重要的工作了,我可以休息一下了,可以给其他线程一些工作机会啦!”
如果你觉得一个线程不那么重要,或者优先级非常低,而且又害怕它会占用太多的 CPU 资源,那么可以在适当的时候调用 Thread.yield 方法,给予其他重要线程更多的工作机会。
源码(JDK8)
/**
* 等待此线程死亡。
*
* 此方法的调用与调用的行为完全相同 join(0)
*
* @throws InterruptedException–如果任何线程中断了当前线程。引发此异常时,将清除当前线程的中断状态。
*/
public final void join()throws InterruptedException{
join(0);
}
/**
* 等待此线程死亡的时间最多为给定的毫秒数。
*
* millis 为0表示永远等待。
*
* 当前实现使用了调用 this.wait 的循环,基于 this.isAlive 为条件。
*
* 当线程终止时,调用 this.notifyAll 方法。建议应用程序不要在线程实例上使用wait、notify或notifyAll。
*
* @param millis 毫秒–以毫秒为单位的等待时间
*
* @throws IllegalArgumentException–如果millis的值为负
*
* InterruptedException–如果任何线程中断了当前线程。引发此异常时,将清除当前线程的中断状态。
*/
public final synchronized void join(long millis)
throws InterruptedException{
long base=System.currentTimeMillis();
long now=0;
//millis的值不能为负
if(millis< 0){
throw new IllegalArgumentException("timeout value is negative");
}
//millis 为0表示永远等待
if(millis==0){
while(isAlive()){
wait(0);
}
}else{
while(isAlive()){
long delay=millis-now;
if(delay<=0){
break;
}
// 线程等待指定时间
wait(delay);
now=System.currentTimeMillis()-base;
}
}
}
关于 wait 的源码解读请参考我的博客——结合JDK源码图文详解 wait 和 notify 的工作原理
/**
* 对调度程序的一个提示,表示当前线程愿意放弃当前对处理器的使用。
*
* 调度程序可以随意忽略此提示。
*
* yield 是一种启发式的尝试,旨在改善线程之间的相对进程,否则会过度使用CPU。
*
* 它的使用应该与详细的分析和基准测试相结合,以确保它实际具有预期的效果。
*
* 使用这种方法很少合适。
*
* 它可能对调试或测试有用,因为它可能有助于再现由于竞争条件而产生的bug。
*
* 在设计并发控制结构(如java.util.concurrent.locks包中的结构)时,它可能也很有用。
*/
public static native void yield();
实践
package com.shockang.study.java.concurrent.thread.join;
public class JoinDemo {
public volatile static int i = 0;
public static class AddThread extends Thread {
@Override
public void run() {
for (i = 0; i < 10000000; i++) ;
}
}
public static void main(String[] args) throws InterruptedException {
AddThread at = new AddThread();
at.start();
at.join();
System.out.println(i);
}
}
package com.shockang.study.java.concurrent.thread.join;
/**
* 中断状态可以检测,并在应用上作出相应
* 如果应用不相应中断,则T1永远不会退出
*
* @author Shockang
*/
public class YieldDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Interrupted!");
break;
}
Thread.yield();
}
}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
}