本节书摘来异步社区《Java线程与并发编程实践》一书中的第1章,第1.2节,作者: 【美】Jeff Friesen,更多章节内容可以访问云栖社区“异步社区”公众号查看。

1.2 操作更高级的线程任务

之前的线程任务都和如何配置一个线程对象以及启动关联的线程相关。不过,Thread类也能支持更多高级的任务,包括中断其他线程、将线程join到另一条线程中以及致使线程睡眠。

1.2.1 中断线程

Thread类提供了一种线程可以中断其他线程的机制。当一个线程被中断时,它会抛出java.lang.InterruptedException。这一机制由下面的3种方法构成。

void interrupt():中断调用此方法的Thread对象所关联的线程。当一条线程由于调用了Thread的sleep()或者join()方法(这一章后面会讨论到)而被阻塞住时,该线程的中断状态就会被清除,同时抛出InterruptedException。否则,除了会设置中断状态,其他动作也会取决于当前线程的行为相应地发生(详情参见JDK文档)。
static boolean interrupted():验证当前线程是否已经中断,在这个例子中会返回true。该线程的中断状态会被这个方法清除掉。
boolean isInterrupted():验证线程是否已经中断,这个例子中会返回true。该线程的中断状态不受此方法的影响。
我创建了一个应用程序来演示线程中断,见清单1-2。

清单1-2 线程中断示例

public class ThreadDemo
{public static void main(String[] args){Runnable r = new Runnable(){@Overridepublic void run(){String name = Thread.currentThread().getName();int count = 0;while (!Thread.interrupted())System.out.println(name + ": " + count++);}};Thread thdA = new Thread(r);Thread thdB = new Thread(r);thdA.start();thdB.start();while (true){double n = Math.random();if (n >= 0.49999999 && n <= 0.50000001)break;}thdA.interrupt();thdB.interrupt();}
}

默认的主线程首先创建runnable对象,用于获取当前线程的名称。这个runnable对象随后声明了一个计数变量并且进入到while循环中,重复打印线程名称和计数变量的值,同时不断递增计数变量的值,直到该线程被中断。

接下来,默认的主线程创建了一对Thread对象,它们执行runnable并启动这些后台线程。

为了给这些后台线程一些时间以便在中断之前打印几条消息,默认主线程进入了一个基于while的忙循环,该循环语句就是拿来消耗一些时间的。这个循环会重复地获取随机数直到数字落入一段狭窄的区间内。

注意:
 

因为会浪费处理器时间,忙循环不是一个好主意。在本章后面我会展示一个更好的解决方案。
while循环终止之后,默认的主线程在每个后台的线程对象上执行interrupt()方法。每个后台线程在下一次执行Thread.interrupted()时,会返回true``并且同时终止循环。

编译清单1-2(javac ThreadDemo.java),并运行最终程序(java ThreadDemo)。你应该能看到包含递增计数变量的消息在Thread-0和Thread-1之间交替。示例如下:

Thread-1: 67
Thread-1: 68
Thread-0: 768
Thread-1: 69
Thread-0: 769
Thread-0: 770
Thread-1: 70
Thread-0: 771
Thread-0: 772
Thread-1: 71
Thread-0: 773
Thread-1: 72
Thread-0: 774
Thread-1: 73
Thread-0: 775
Thread-0: 776
Thread-0: 777
Thread-0: 778
Thread-1: 74
Thread-0: 779
Thread-1: 75

1.2.2 等待线程

线程(如默认的主线程)会偶尔启动另一个线程去操作单调的计算、下载大文件或者操作一些其他的耗时任务。在结束它自己的任务之后,这个启动工作线程的线程就准备着处理工作线程的结果,同时等待该工作线程“寿终正寝”。

Thread类提供了3种join()方法,允许调用线程等待执行此方法的线程对象所关联的线程执行完毕。

  • void join():无限期地等待直至该线程死亡。当任意线程中断当前线程的时候,InterruptedException就会抛出。如果该异常被抛出,该线程的中断状态就会被清除。
  • void join(long millis):该线程死亡之前最多等待millis毫秒。如果传递0作为参数就会无限期地等待——``join()其实就调用了join(0)方法。如果millis是负数,那么就会导致IllegalArgument Exception被抛出。当任意线程中断了当前线程,就会导致InterruptedException被抛出,如果该异常被抛出,该线程的中断状态会被清除。
  • void join(long millis, int nanos):该线程死亡之前最多等待millis毫秒加nanos纳秒。当millis是负数、nanos是负数或者nanos大于999999的时候,会导致IllegalArgumentException被抛出。当任意线程中断了当前线程,就会导致InterruptedException被抛出,如果该异常被抛出,该线程的中断状态会被清除。
    为了演示不含参数的join()方法,我创建了一个应用程序来计算数学中的常量pi到小数点后50000位。它是根据17世纪早期的一位英国数学家John Machin发明的算法来计算的。这一算法首先计算pi/4 = 4 × arctan(1/5)−arctan(1/239),然后把结果乘以4得到pi的值。因为反正切函数使用了幂级的条件计算,条件越多,pi的值会越精确(从到小数点后多少位这方面来看)。清单1-3展示了源代码。

清单1-3 演示Thread Joining

import java.math.BigDecimal;public class ThreadDemo
{// constant used in pi computationprivate static final BigDecimal FOUR = BigDecimal.valueOf(4);// rounding mode to use during pi computationprivate static final int roundingMode = BigDecimal.ROUND_HALF_EVEN;private static BigDecimal result;public static void main(String[] args){Runnable r = () ->{result = computePi(50000);};Thread t = new Thread(r);t.start();try{t.join(); }catch (InterruptedException ie){// Should never arrive here because interrupt() is never// called. }System.out.println(result);}/** Compute the value of pi to the specified number of digits after the* decimal point. The value is computed using Machin's formula:** pi/4 = 4*arctan(1/5)-arctan(1/239)** and a power series expansion of arctan(x) to sufficient precision.*/public static BigDecimal computePi(int digits){int scale = digits + 5;BigDecimal arctan1_5 = arctan(5, scale);BigDecimal arctan1_239 = arctan(239, scale);BigDecimal pi = arctan1_5.multiply(FOUR).subtract(arctan1_239).multiply(FOUR);return pi.setScale(digits, BigDecimal.ROUND_HALF_UP);}/** Compute the value, in radians, of the arctangent of the inverse of* the supplied integer to the specified number of digits after the* decimal point. The value is computed using the power series* expansion for the arc tangent:** arctan(x) = x-(x^3)/3+(x^5)/5-(x^7)/7+(x^9)/9 ...*/public static BigDecimal arctan(int inverseX, int scale){BigDecimal result, numer, term;BigDecimal invX = BigDecimal.valueOf(inverseX);BigDecimal invX2 = BigDecimal.valueOf(inverseX * inverseX);numer = BigDecimal.ONE.divide(invX, scale, roundingMode);result = numer;int i = 1;do{numer = numer.divide(invX2, scale, roundingMode);int denom = 2 * i + 1;term = numer.divide(BigDecimal.valueOf(denom), scale,roundingMode);if ((i % 2) != 0)result = result.subtract(term);elseresult = result.add(term);i++; }while (term.compareTo(BigDecimal.ZERO) != 0);return result;}
}

默认的主线程首先创建了一个runnable去计算pi到小数点后50000位,然后把结果赋值给名为result的java.math.BigDecimal对象。为了代码简洁,这里使用了lambda表达式。

这个线程随后创建了一个Thread对象去执行runnable并启动了一个工作线程来执行操作。

这里,默认的主线程在该Thread对象上调用了join()方法等待工作线程死亡。当工作线程死亡了,默认主线程会打印出BigDecimal对象的值。

编译清单1-3的代码(javac ThreadDemo.java)并运行最终程序(java ThreadDemo)。我观察到的前段部分输出如下:

3.1415926535897932384626433832795028841971693993751058209749445923078164062
862089986280348253421170679821480865132823066470938446095505822317253594081
284811174502841027019385211055596446229489549303819644288109756659334461284
756482337867831652712019091456485669234603486104543266482133936072602491412
737245870066063155881748815209209628292540917153643678925903600113305305488
204665213841469519415116094330572703657595919530921861173819326117931051185
4807446237996274956735188575272489122793818301194912983367336244065664308
6021394946395224737190702179860943702770539217176293176752384674818467669
405132000568127

1.2.3 线程睡眠

Thread类声明了一对静态方法致使线程睡眠(暂时性地停止执行)。

void sleep(long millis):睡眠millis毫秒数。线程睡眠的实际的毫秒数取决于系统定时器和调度器的精度。如果millis是负数,那么就会导致IllegalArgumentException+被抛出。当任意线程中断了当前线程,就会导致javascript InterruptedException被 抛出,如果该异常被抛出,该线程的中断状态会被清除。
void sleep(long millis, int nanos):睡眠millis``毫秒数和nanos纳秒数。实际睡眠的毫秒数和纳秒数取决于系统定时器和调度器的精度。当millis是负数,nanos是负数或者nanos大于999999的时候,会导致IllegalArgumentException被抛出。当任意线程中断了当前线程,就会导致InterruptedException被抛出,如果该异常被抛出,该线程的中断状态会被清除。
sleep()方法相较于忙循环更好,因为它们不会浪费处理器周期。

我已经重构了清单1-2的应用程序来展示线程睡眠。请看清单1-4。

清单1-4 线程睡眠示例

public class ThreadDemo
{public static void main(String[] args){Runnable r = new Runnable(){@Overridepublic void run(){String name = Thread.currentThread().getName();int count = 0;while (!Thread.interrupted())System.out.println(name + ":" + count++);}};Thread thdA = new Thread(r);Thread thdB = new Thread(r);thdA.start();thdB.start();try{Thread.sleep(2000);}catch (InterruptedException ie){}thdA.interrupt();thdB.interrupt();}
}

清单1-2和清单1-4唯一的不同之处就是使用Thread.sleep(2000)替代了忙循环,睡眠了2秒。

编译清单1-4 (javac ThreadDemo.java),运行最终程序(java ThreadDemo)。由于睡眠时间是大概的时间,所以在多次运行中打印出的行数会有差异。但是,这种差异不会特别大。举个例子,你不会在某次运行看到10行,而在另外一次运行中看到1000万行。

《Java线程与并发编程实践》—— 1.2 操作更高级的线程任务相关推荐

  1. 《Java线程与并发编程实践》—— 2.3 谨防活跃性问题

    本节书摘来异步社区<Java线程与并发编程实践>一书中的第2章,第2.3节,作者: [美]Jeff Friesen,更多章节内容可以访问云栖社区"异步社区"公众号查看. ...

  2. 《Java线程与并发编程实践》—— 2.6 小结

    本节书摘来异步社区<Java线程与并发编程实践>一书中的第2章,第2.6节,作者: [美]Jeff Friesen,更多章节内容可以访问云栖社区"异步社区"公众号查看. ...

  3. auto.js停止所有线程_Java线程与并发编程实践:深入理解volatile和final变量

    同步有两种属性:互斥性和可见性.synchronized关键字与两者都有关系.Java同时也提供了一种更弱的.仅仅包含可见性的同步形式,并且只以volatile关键字关联. 假设你自己设计了一个停止线 ...

  4. java多线程 门闩_Java线程与并发编程实践----同步器(倒计时门闩,同步屏障)...

    Java提供的synchronized关键字对临界区进行线程同步访问.由于基于synchronized很难 正确编写同步代码,并发工具类提供了高级的同步器.倒计时门闩(countdown latch) ...

  5. Java线程与并发编程实践----同步器(Phaser)

    Phaser是一个更加弹性的同步屏障.和同步屏障一样,一个phaser使得一组线程在 屏障上等待,在最后一个线程到达之后,这些线程才得以继续执行.phaser也提供了和barrier action等价 ...

  6. java并发编程实践(2)线程安全性

    [0]README 0.0)本文部分文字描述转自:"java并发编程实战", 旨在学习"java并发编程实践(2)线程安全性" 的相关知识: 0.1)几个术语( ...

  7. 《Java并发编程实践》学习笔记之一:基础知识

    <Java并发编程实践>学习笔记之一:基础知识 1.程序与进程 1.1 程序与进程的概念 (1)程序:一组有序的静态指令,是一种静态概念:  (2)进程:是一种活动,它是由一个动作序列组成 ...

  8. Java并发编程实战_一线大厂架构师整理:java并发编程实践教程

    并发编程是Java语言的重要特性之一, 在Java平台上提供了许多基本的并发功能来辅助开发多线程应用程序.然而,这些相对底层的并发功能与上层应用程序的并发语义之间并不存在一种简单而直观的映射关系.因此 ...

  9. java并发编程实践_Java并发编程实践如何正确使用Unsafe

    一.前言 Java 并发编程实践中的话: 编写正确的程序并不容易,而编写正常的并发程序就更难了.相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各 ...

最新文章

  1. php nl2br() 函数
  2. C语言函数到.h文件,求助C语言大佬 , 只会写到一个.c文件里 ,不会用.h头文件...
  3. 【网络通信与信息安全】之深入解析进程之间的通信方式
  4. java .listfiles_Java File.listFiles()
  5. linux网页视频黑边,ffmpeg去除视频黑边命令
  6. mysql 当前时间的一周后_mysql查询当前时间,一天内,一周,一个月内的sql语句...
  7. [北航软工]技术规格说明书
  8. BlockUI详细用法
  9. 知群产品经理必修TOP班-31期学习笔记
  10. 2019java面试(二)
  11. abap语言去除重复项怎么写
  12. IT业软件测试的男女性别差异渐趋消褪
  13. IP-guard项目实施前情况调查表
  14. 1838公共政策概论 (2)
  15. MySQL 内连接、外连接、全连接
  16. 【题解】洛谷P6006 [USACO20JAN]Farmer John Solves 3SUM G
  17. 转:solr 从数据库导入数据,全量索引和增量索引(实例配置原理)
  18. php判断三个数为对子,豹子| 简单粗暴方法
  19. 一键安装5个系统方法,简单易学轻松上手
  20. 插曲一 解决-source 1.4 中不支持注释和泛型问题

热门文章

  1. BZOJ 2466 [中山市选2009]树(高斯消元)
  2. Python爬虫小白入门(六)爬取披头士乐队历年专辑封面-网易云音乐
  3. cocos2d-x-3.x 配置(1)win环境搭建
  4. 机房收费系统系列二:MDI子窗体和主窗体显示
  5. AAC Explicit or Implicit SBR PS issue
  6. #pragma 的几种用法
  7. 二叉排序树(概念,查找,插入,删除)
  8. 进入已经打开的pyrebox_PyREBox-可用Python编写脚本的逆向工程沙盒
  9. oracle otl,使用OTL调用Oracle的存储函数
  10. 狂神说 es笔记_【开源推荐】专门为程序员朋友量身打造的笔记软件—— Boostnote...