任务和线程的启动很容易。在大多数情况下我们都会让他们运行直到结束,或是让他们自行停止。但是,有时我们希望提前结束任务或是线程,可能是因为用户请求取消,或是线程在规定时间内没有结束,或是出现了一些问题迫使线程要提前结束。

  强制一个线程或是服务立即停止,可能会造成共享数据状态不一致的问题,比如,两个线程正对一个共享数据进行操作,然后被突然杀死,这样会对数据造成不确定性的影响。Java中没有提供任何机制来安全的终止线程,但它提供了中断,这种协作机制,“提醒”线程可以自己结束自己线程。这种机制提供了更好的灵活性,因为任务本身的代码比发出取消请求的代码更清楚如何执行停止工作。

1、使用“标志”变量取消任务

 public class PrimeGenerator implements Runnable {
private final List<BigInteger> primes = new ArrayList<>();
// 标志变量,设置为volatile,保证可见性
private volatile boolean canceled = false;
@Override
public void run() {
BigInteger p = BigInteger.ONE;
// 依靠标志位判断是否结束线程
while(!canceled){
p = p.nextProbablePrime();
synchronized (this){
primes.add(p);
}
}
}
// 取消
public void cancel(){canceled = true;}
//返回结果
public synchronized List<BigInteger> get(){
return primes;
}
}

  上述代码设置一个volatile “已请求取消”标志,而任务将定期查看该标志。 PrimeGenerator 将持续的枚举素数,直到标志位被设置为取消结束。PrimeGenerator  每次枚举素数时候都会检查canceled标志位是否被改变。

 public List<BigInteger> aPrimes() throws InterruptedException {
PrimeGenerator generator = new PrimeGenerator();
new Thread(generator).start();
try{
// 睡眠1秒
TimeUnit.SECONDS.sleep(1);
}finally {
// 1秒后取消
generator.cancel();
}
return generator.get();
}

  调用素数生成器运行1秒后取消,值得注意的是,素数生成器可能不会在1秒后“准时”停止,因为他可能此时刚好在while内执行。取消语句放在finally语句执行,保证该语句一定会被执行。

2、取消策略

  在设计良好的程序中,一个可取消的任务必须拥有取消策略,这个策略详细定义取消操作的“How”、“When”、“What”,即代码如何(How)请求取消该任务,任务在何时(When)检查是否已经请求了取消,以及在响应时执行那些(What)操作。

  在上述代码中,PrimeGenerator采用了简单的取消策略:客户代码通过canceled来请求取消,PrimeGenerator在每次执行搜索前首先检查是否存在取消请求,如果存在则退出。

3、中断线程

  PrimeGenerator 中取消机制之所以能成功,是因为程序会不间断定期的检查标志位的状态是否被改变。但是,如果程序调用了一个阻塞方法,例如,BlockingQueu.put()那么可能会出现问题,即任务可能永远不会检查取消标志。【阻塞队列不了解的看看这篇博客:http://www.cnblogs.com/moongeek/p/7832855.html#_label3

 // 不推荐的写法
public class BrokenPrimeProducer extends Thread {
// 阻塞队列
private final BlockingQueue<BigInteger> queue;
// 中断位
private volatile boolean canceled = false; public BrokenPrimeProducer(BlockingQueue<BigInteger> queue){
this.queue = queue;
} @Override
public void run(){
try {
BigInteger p = BigInteger.ONE;
while (!canceled) {
// PUT操作可能会被阻塞,将无法检查 canceled 是否变化,因而无法响应退出
queue.put(p = p.nextProbablePrime());
}
}catch (InterruptedException ex){}
} public void cancel(){
canceled = true;
}
}

  如果阻塞队列在 put()  操作被阻塞,此时,即使我们调用cancel() 方法将状态变量改变,进程也无法检查到改变,因为会一直阻塞下去。

  每个Thread都有一个boolean类型的中断状态。当中断线程时,改状态会被置为true。Thread中包含的中断方法如下。其中 inturrept() 会将中断状态置为true,而 isInterrupted() 方法会返回当前的中断状态,而 interrupted() 方法则会清除当前状态,并返回它之前的值。

 public class Thread{
public void inturrept(){......}
public boolean isInterrupted(){......}
public static boolean interrupted(){......}
}

  通常情况下,如果一个阻塞方法,如:Object.wait()、Thread.sleep()Thread.join() 时,都会去检查中断状态的值,发现中断状态变化时都会提前返回并响应中断:清除中断状态,并抛出InterruptedException异常

  该注意的是,中断操作并不会真正的中断一个正在运行的线程,而只是发出中断请求,然后由程序在合适的时刻中断自己。一般设计方法时,都需要捕获到中断异常后对中断请求进行某些操作,不能完全忽视或是屏蔽中断请求。

  对上代码进行改进,采用中断进行中断程序执行。代码中有两处可以检测中断:在阻塞的put() 方法中,以及循环开始处的查询中断状态时。其实put() 操作会检测响应异常,在循环开始时可以不进行检测,但这样可以获得更高效的响应性能。

 public class PrimeProducer extends Thread {
// 阻塞队列
private final BlockingQueue<BigInteger> queue; public PrimeProducer(BlockingQueue<BigInteger> queue){
this.queue = queue;
} @Override
public void run(){
try {
BigInteger p = BigInteger.ONE;
while (!Thread.currentThread().isInterrupted()) {
queue.put(p = p.nextProbablePrime());
}
}catch (InterruptedException ex){
// 允许退出线程
}
} public void cancel(){
// 中断
interrupt();
}
}

  中断是实现取消的最合理方式,在取消之外的其他操作中使用中断,都是不合理的。

4、中断策略

  中断策略解释某个中断请求:当发现中断请求时,应该做哪些工作,以多快的速度来响应中断。任务一般不会在其自己拥有的线程中执行,而是在其他某个服务(比如说,在一个其他线程或是线程池)中执行。对于非线程所有者而言(例如,对线程池来说,任何线程池实现之外的代码),应该保存并传递中断状态,使得真正拥有线程的代码才能对中断做出响应。

  比如说,如果你书写一个库函数,一般会抛出InterruptedException作为中断响应,而不会在库函数时候把中断异常捕获并进行提前处理,而导致调用者被屏蔽中断。因为你不清楚调用者想要对异常进行何种处理,比如说,是接收中断后立即停止任务还是进行相关处理并继续执行任务。中断的处理必须由该任务自己决定,而不是由其他线程决定。

  因为在捕获InterruptException 中会同时把中断位恢复,所以,如果想捕获异常后恢复中断位,一般会调用 Thread.currentThread.interrupt() 进行中断位的恢复。

  try {
// dosomething();
} catch (InterruptedException e) {
// 捕获异常后恢复中断位
Thread.currentThread().interrupt();
e.printStackTrace();
}

5、使用Future 来实现取消

  关于Future 对象:ExecutorService.submit 方法将返回一个Future 来描述任务。

 public interface Future<V> {
// 是否取消线程的执行
boolean cancel(boolean mayInterruptIfRunning);
// 线程是否被取消
boolean isCancelled();
//线程是否执行完毕
boolean isDone();
// 立即获得线程返回的结果
V get() throws InterruptedException, ExecutionException;
// 延时时间后再获得线程返回的结果
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
 public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
Future future = service.submit(new TheradDemo()); try {
// 可能抛出异常
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
//终止任务的执行
future.cancel(true);
}
}

  Future 中的  cancel(boolean mayInterruptIfRunning) 接受一个布尔参数表示取消操作是否成功。如果Future.get()  抛出异常,如果你不需要得到结果时,就可以通过cancel(boolean) 来取消任务。

  对于线程池中的任务,如果想想要取消执行某任务,不宜中断线程池,因为你不知道中断请求到达时正在执行什么任务,所以只能通过cancel(boolean) 来定向取消特定的任务。

6、关闭ExecutorService

  线程池相关对象ExecutorService 提供了两种关闭的方法:使用 shutdown() 正常关闭,他先把线程池状态设置为SHUTDOWN ,禁止再向线程池提交任务,然后把线程池中的任务全部执行完毕,就关闭线程池。这种方法速度较慢,但是更安全。以及使用shutdownNow() 首先关闭正在执行的任务,然后返回所有尚未启动的任务清单。这种方法速度快,但风险也大,因为有的任务可能执行了一般被关闭。

Java多线程学习之线程的取消与中断机制的更多相关文章

  1. Java多线程学习篇——线程的开启

    随着开发项目中业务功能的增加,必然某些功能会涉及到线程以及并发编程的知识点.笔者就在现在的公司接触到了很多软硬件结合和socket通讯的项目了,很多的功能运用到了串口通讯编程,串口通讯编程的安卓端就是 ...

  2. Java多线程学习(二)---线程创建方式

    线程创建方式 摘要: 1. 通过继承Thread类来创建并启动多线程的方式 2. 通过实现Runnable接口来创建并启动线程的方式 3. 通过实现Callable接口来创建并启动线程的方式 4. 总 ...

  3. Java多线程学习总结--线程概述及创建线程的方式(1)

    在Java开发中,多线程是很常用的,用得好的话,可以提高程序的性能. 首先先来看一下线程和进程的区别: 1,一个应用程序就是一个进程,一个进程中有一个或多个线程.一个进程至少要有一个主线程.线程可以看 ...

  4. JAVA多线程学习十一-线程锁技术

    前面我们讲到了synchronized:那么这节就来将lock的功效. 一.locks相关类 锁相关的类都在包java.util.concurrent.locks下,有以下类和接口: |---Abst ...

  5. Java多线程学习之线程池源码详解

    0.使用线程池的必要性 在生产环境中,如果为每个任务分配一个线程,会造成许多问题: 线程生命周期的开销非常高.线程的创建和销毁都要付出代价.比如,线程的创建需要时间,延迟处理请求.如果请求的到达率非常 ...

  6. Java多线程学习(三)---线程的生命周期

    线程生命周期 摘要: 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态.在线程的生命周期中,它要经过新建(New).就绪(Runnable).运行(Running).阻塞 ...

  7. JAVA多线程学习七-线程池

    为什么用线程池 1.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率 例如: 记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3 如果T1+T3> ...

  8. Java多线程学习总结--线程同步(2)

    线程同步是为了让多个线程在共享数据时,保持数据的一致性.举个例子,有两个人同时取钱,假设用户账户余额是1000,第一个用户取钱800,在第一个用户取钱的同时,第二个用户取钱600.银行规定,用户不允许 ...

  9. 【转】Java多线程学习

    来源:http://www.cnblogs.com/samzeng/p/3546084.html Java多线程学习总结--线程概述及创建线程的方式(1) 在Java开发中,多线程是很常用的,用得好的 ...

随机推荐

  1. C# OleDbConnection对特定部分Excel的数据读取

    最近在写winform程序,先来一个简单的. 读取特定部分Excel的数据读取,读取Excel第30行开始到H列的数据 using System;using System.Collections.Ge ...

  2. js事件处理

    1.js中常用的事件处理程序(event Handler) onabort 用户终止了页面的加载 onblur 用户离开了对象 onchange 用户修改了对象 onclick 用户点击了对象 one ...

  3. window主机和centos主机之间相互传送文件

    命令实现linux和window文件传送 一:下载配置pscp软件从http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html下载p ...

  4. setInterval传递参数

    参照:https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout 平时我们使用定时器的时 ...

  5. Flask从入门到精通之链接的使用

    在Web开发中,任何具有多个路由的程序都需要可以连接不同页面的链接,例如导航条. 在模板中直接编写简单路由的URL 链接不难,但对于包含可变部分的动态路由,在模板中构建正确的URL 就很困难.而且,直 ...

  6. grub覆盖mbr引导系统

    grub覆盖mbr引导系统 0.个人PC,WIN 7 + Kali,easybcd 不起作用,需要制作 kali 安装盘 PS:推荐使用 universal usb installer 制作. 方案一 ...

  7. mysql 练习 和链接 pymysql 练习题

    python操作数据库 1. 查询student表的所有记录 2. 查询student表的第2条到第4条记录 3. 查询所有学生的学号(id).姓名(name)和报读课程(department)的信息 ...

  8. pg_stat_statements跳过的坑

    pg_stat_statements跳过的坑 原本以为只是一个简单的插件扩展安装,三下五除二就能搞定,结果搞了很久也没找到问题所在.首先pg_stat_statements已经安装成功,且已经能够使用 ...

  9. window本地运行mapreduce程序

    mapreduce的运行方式一般有两种,一是从本地导出一个jar包,在传到虚拟机上运行,这样调试起来非常的不方便,如果出现错误就需要重新导出jar包. 第二种方式是在本地直接运行,但是在运行前需要进行 ...

  10. tcp ip三次握手链接和四次挥手断开

      先来个整体的流程图       一 三次握手目的是为了建立连接... 1 核心的就是client端和service端,进行数据"报文" 交换 2 报文,目的是互相通知,确认链接 ...