本文共 3816 字,大约阅读时间需要 12 分钟。
本文隶属于专栏《100个问题搞定Java并发》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!
本专栏目录结构和参考文献请见
在很多情况下,线程之间的协作和人与人之间的协作非常类似。
一种非常常见的合作方式就是分工合作。
以我们非常熟悉的软件开发为例,在一个项目进行时,总是应该有几位号称是“需求分析师”的同事,先对系统的需求和功能点进行整理和总结,以书面形式给出份需求说明或者类似的参考文档,然后,软件设计师、研发工程师オ会一拥而上,进行软件开发。
如果缺少需求分析师的工作输出,那么软件研发的难度可能会比较大。
因此,作为名软件研发人员,总是喜欢等待需求分析师完成他应该完成的任务后,才愿意投身工作。
简单地说,就是研发人员需要等待需求分析师完成他的工作,然后才能进行研发。
将这个关系对应到多线程应用中,很多时候,一个线程的输入可能非常依赖于另外个或者多个线程的输出,此时,这个线程就需要等待依赖线程执行完毕,才能继续执行。
JDK 提供了 join() 操作来实现这个功能。
如下所示,显示了两个 join ()方法:
public final void join()throws InterruptedExceptionpublic final synchronized void join(long millis)throws InterruptedException
第一个 join ()方法表示无限等待,它会一直阻塞当前线程,直到目标线程执行完毕。
第二个方法给出了一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为“等不及了”,而继续往下执行。
英文 join 的翻译,通常是加入的意思。
这个意思在这里也非常贴切。
因为一个线程要加入另外一个线程,最好的方法就是等着它一起走。
join() 方法的本质是让调用线程 wait() 方法在当前线程对象实例上。(详情请见下面的源码解读)
另外一个比较有趣的方法是 Thread.yield ,它的定义如下:
public static native void yield();
这是一个静态方法,一旦执行,它会使当前线程让出 CPU 。
但要注意,让出 CPU 并不表示当前线程不执行了。
当前线程在让出 CPU 后,还会进行 CPU 资源的争夺,但是是否能够再次被分配到就不一定了。
因此,对 Thread.yield 方法的调用就好像是在说:“我已经完成了一些最重要的工作了,我可以休息一下了,可以给其他线程一些工作机会啦!”
如果你觉得一个线程不那么重要,或者优先级非常低,而且又害怕它会占用太多的 CPU 资源,那么可以在适当的时候调用 Thread.yield 方法,给予其他重要线程更多的工作机会。
/** * 等待此线程死亡。 * * 此方法的调用与调用的行为完全相同 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 的源码解读请参考我的博客——
/** * 对调度程序的一个提示,表示当前线程愿意放弃当前对处理器的使用。 * * 调度程序可以随意忽略此提示。 * * 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(); }}
转载地址:http://kugji.baihongyu.com/