Java没有提供任何机制来安全地终止线程,但是它提供了中断(Interruption).这是一种协作机制,能够使一个线程终止另一个线程当前的工作。

  • 在对一个线程对象调用Thread.interrupted()方法之后,一般情况下对这个线程不会产生任何影响。因为调用Thread.interrupted()方法只是将增线程的中断标志位设置为true。
  • 如果一个线程被调用Thread.interrupted()方法之后,如果它的状态是阻塞状态或者是等待状态,而且这个状态正是因为正在执行的wait、join、sleep线程造成的,那么是会改变运行的结果(抛出InterruptException异常)

1.线程终止

由于Java没有提供任何机制来安全地终止线程,那么我们应该如何终止线程呢?下面我们提供三种线程终止的方法:

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  2. 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
  3. 使用interrupt方法中断线程。

1.1 使用退出标志

当run方法执行完后,线程就会退出。但有时run方法是永远不会结束的。如在服务端程序中使用线程进行监听客户端请求,或是其他的需要循环处理的任务。在这种情况下,一般是将这些任务放在一个循环中,如while循环。如果想让循环永远运行下去,可以使用while(true){……}来处理。但要想使while循环在某一特定条件下退出,最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。

public class ThreadFlag implements Runnable{private volatile boolean exit=false;@Overridepublic void run() {while (!exit){///do somethingtry {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("-----------ThreadFlag shutdown----------");}public static void main(String[] args) throws InterruptedException {ThreadFlag threadFlag=new ThreadFlag();Thread thread=new Thread(threadFlag);thread.start();Thread.sleep(3000);threadFlag.exit=true;thread.join();System.out.println("线程退出");}
}

上面代码使用了一个线程标志位来判断线程是否关闭.通过对线程标志位进行操作来决定线程是否关闭.

1.2 使用stop方法终止线程

使用stop方法可以强行终止正在运行或挂起的线程。我们可以使用如下的代码来终止线程:

public class ThreadStop implements Runnable {@Overridepublic void run() {try {while (true){try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}} finally {System.out.println("-----------ThreadStop shutdown----------");}}public static void main(String[] args) throws InterruptedException {ThreadStop threadStop=new ThreadStop();Thread thread=new Thread(threadStop);thread.start();Thread.sleep(3000);thread.stop();System.out.println("线程退出");}
}

这种方法线程不安全,Java不建议使用这种stop方法关闭线程。

1.3 使用interrupt方法终止线程

使用interrupt方法来终端线程可分为两种情况:

(1)线程处于阻塞状态,如使用了sleep方法。

(2)使用while(!isInterrupted()){……}来判断线程是否被中断。

enum  FuntuionType{FunctionType1,FunctionType2,
}
public class ThreadInterrupt implements Runnable{private FuntuionType funtuionType;public ThreadInterrupt(FuntuionType funtuionType) {this.funtuionType = funtuionType;}@Overridepublic void run() {switch (funtuionType){case FunctionType1:int i = 0;while (!Thread.interrupted()){//do somethingi++;}System.out.println("Thread.interrupted() shutdown");break;case FunctionType2:try {Thread.sleep(50*1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Sleep InterruptedException throws");break;default:break;}}public static void main(String[] args) throws InterruptedException {ThreadInterrupt threadInterrupt=new ThreadInterrupt(FuntuionType.FunctionType2);Thread thread=new Thread(threadInterrupt);thread.start();Thread.sleep(2000);thread.interrupt();System.out.println("线程已经退出");}
}

2. 任务取消

Java中没有一种安全的抢占式方法来停止线程,因此就没有安全的抢占式方法来停止任务。下面我们就来介绍一中协作式的方式来取消一个任务。

2.1 取消标志位

第一种方式就是设置某个“已请求取消”的标志位,而任务周期性的查看这个标志位。如果设置了这个标志位,那么任务就提前结束。

public class PrimeGenerator implements Runnable{private final List<BigInteger> primes=new ArrayList<>();private volatile boolean cancelled = false;private volatile BigInteger p = BigInteger.ONE;@Overridepublic void run() {while (!cancelled){//此方法返回一个整数大于该BigInteger的素数。p = p.nextProbablePrime();synchronized (this) {primes.add(p);}}}public void cancel(){this.cancelled=true;}public synchronized List<BigInteger> get(){return new ArrayList<>(primes);}public static void main(String[] args) throws InterruptedException {PrimeGenerator primeGenerator=new PrimeGenerator();for (int i = 0; i < 10; i++) {Thread thread=new Thread(primeGenerator);thread.start();}Thread.sleep(2000);primeGenerator.cancel();for (BigInteger bigInteger : primeGenerator.get()) {System.out.println(bigInteger);}}
}

2.2 中断 Interrupt

由于PrimeGenerator中的取消机制最终会使得素数的任务进行退出。但是如果使用这个方法中的任务调用了一个阻塞方法,列如BlockingQueue.put,那么就会产生一个严重的问题————任务可能永远不会检查取消标志。因此永远不会结束。所以这个时候我们就采用中断Interrupt来取消任务。

public class PrimeProducer implements Runnable {private final BlockingQueue<BigInteger> bigIntegers;private Thread thread;public void setThread(Thread thread) {this.thread = thread;}public PrimeProducer(BlockingQueue<BigInteger> bigIntegers) {this.bigIntegers = bigIntegers;}private volatile BigInteger p = BigInteger.ONE;@Overridepublic void run() {try {while (!Thread.currentThread().isInterrupted()){p = p.nextProbablePrime();bigIntegers.put(p.nextProbablePrime());}} catch (InterruptedException e) {e.printStackTrace();}}public void cancel(){thread.interrupt();}public BlockingQueue<BigInteger> getBigIntegers() {return bigIntegers;}public static void main(String[] args) throws InterruptedException {BlockingQueue<BigInteger> bigIntegers=new LinkedBlockingQueue<>();PrimeProducer primeProducer=new PrimeProducer(bigIntegers);Thread thread=new Thread(primeProducer);primeProducer.setThread(thread);thread.start();Thread.sleep(1000);primeProducer.cancel();for (BigInteger bigInteger : primeProducer.getBigIntegers()) {System.out.println(bigInteger);}}
}

每个线程都有一个Boolean类型的中断状态。当中断线程发生的时候,这个线程就把这个中断状态设置为true。咋Thread中包含了中断线程以及查询线程中断状态的方法。

Thrad中的中断方法:

public class Thread{public void interrupt(){}public boolean isInterrupted(){}public static boolean interrupted(){}
}
  • interrupt()方法能够中断目标线程
  • isInterrupted方法能够返回目标线程的中断状态
  • interrupted静态方法能够将清除当前线程的中断状态,并返回它之前的值,这也是清除中断状态的唯一方法

阻塞库方法,比如Thread.sleep和Object.wait和Thread.join等,都会检查线程何时中断,并且在发生中断时提前返回。他们在响应中断时只需的操作包括:清除中断状态,抛出InterruptExecption异常,表示阻塞操作由于中断而提前结束。

当线程在非阻塞状态下中断时,它的中断状态将被设置为true,然后根据将取消的操作来检查中断状态以判断发生了中断。通过这样的方法,中断操作将变得有粘性————如果不触发InterruptException,那么中断将一直保持,直到明确的清除中断状态。

总结:
对中断的正确理解是:它并不会真正地中断在一个正在运行的线程,而是发出中断请求,然后由线程在下一个合适的时刻中断自己。(这些合适的时刻称为取消点)。有些方法,比如wait、slee和join等,将严格处理这种请求,当他们收到中断请求或者开始执行发现某个已经被设置好的中断状态的时候,将抛出一个异常。
中断策略

最合理的中断策略使某
种线程级(Thread-Level)取消操作或者服务级的取消操作:尽快退出,在必要时候进行清理,通知某个所有线程已经退出。当前检查到中断请求时候,任务不需要放弃所有的操作————它可以推迟处理中断请求,并找到合适的时刻。因此需要记住中断请求,并在完成当前任务之后抛出InterruptExeception或者表示已经收到中断请求。这项技术能够确保在更新过程中发生中断时,数据结构不会发生破坏。除了将InterruptException传递给调用者外还需要执行额外的操作,那么应该在捕获InterruptException之后恢复中断状态:

Thread.currentThread().interrupt();

响应中断

当中断异常发生的时候,我们有两种方式进行响应中断请求:

  • 传递异常(可能在执行某个特定于任务的清除操作之后):从而使你的方法也可以是中断的阻塞方法
  • 恢复中断状态:从而使调用占中的上层代码能对其进行处理。如果不想处理中断请求,一种标准的方法就是通过再次调用interrupt来恢复中断状态。你不能屏蔽中断状态,你只能恢复中断状态。

2.3 通过Future来实现取消

我们已经使用了一种抽象机制来管理任务的生命周期,处理异常,下面我们来介绍一种使用Future类来实现任务取消。

public class TimeRun {private static ExecutorService executorService= Executors.newFixedThreadPool(5);public static void timeRun(Runnable runnable, long timeout, TimeUnit timeUnit){Future<?> submit = executorService.submit(runnable);try {submit.get(timeout,timeUnit);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();} catch (TimeoutException e) {//接下来任务将被取消e.printStackTrace();} finally {//如果任务已经结束,那么执行取消操作也不会带来任何影响//如果任务正在运行,那么将会被中断submit.cancel(true);}}
}

当Future.get抛出InterruptException或者TimeoutException时,如果你知道不再需要结果,那么就可以调用Future.cancel来取消任务。

2.4 处理不可中断的阻塞

如果一个线程由于执行同步的Socket I/O 或者等待获得内置锁而阻塞,那么中断请求只能设置线程的中断状态,除此之外没有其他任何作用。对于那些由于执行不可中断操作而被阻塞的线程,可以使用类似于中断的手段来停止这些线程。

public class ReaderThread extends Thread {private final Socket socket;private final InputStream inputStream;public static final int BUFSIZE=1024;public ReaderThread(Socket socket) throws IOException {this.socket = socket;this.inputStream=socket.getInputStream();}@Overridepublic void interrupt() {try {socket.close();} catch (IOException e) {e.printStackTrace();}finally {super.interrupt();}}@Overridepublic void run() {byte [] buf=new byte[BUFSIZE];try {while (true) {int count= inputStream.read(buf);//dosomething}} catch (IOException e) {e.printStackTrace();}}
}

通过重写Interrupt方法将非标准的取消操作封装到Thread中,实现中断功能

3. 停止线程的服务

正确的封装原则是:除非拥有某个线程,否则不能对线程进行操作。比如,中断线程,或者修改线程的优先级等等。

服务应该提供生命周期方法(Lifecycle Method)来关闭它自己以及它所拥有的线程。这样应用程序关闭服务的时候,服务就可以关闭所有线程了。对于持有线程的服务,只要服务的存在时间大于创建线程的方法的存在时间,就应该提供生命周期方法。

3.1 关闭ExecutorService

关闭ExecutorService提供了两种关闭方法:shutdown和shutdownNow方法

强行关闭的速度更快,但是风险也更大,因为任务很可能执行到一半就结束
正常关闭的速度虽然慢,但是却更为安全,因为ExecutorService会一直等到队列中的所有任务都执行完成之后才关闭。

4. JVM关闭

JVM关闭应用程序可以分为两种方式:

正常关闭:当最后一个“正常(非守护)”线程结束的时候,或者调用了System.exit(0)时,或者通过其他特定于平台的方法关闭(比如发出了SIGNT信号或者Ctrl-C)
强行关闭:通过调用Runtime.halt或者在操作系统中“杀死”JVM进程来强行关闭JVM

4.1 关闭钩子

在正常关闭中,JVM首先调用以及注册的关闭钩子(shutdown Hook)。关闭钩子是指通过Runntime.addShutdwonHook注册的但是尚未开始的线程。JVM不能保证这些线程的执行顺序。在关闭应用程序线程时,如果有线程正在运行,那么这些线程接下来将于关闭进程并发执行。

public class JavaHook {private static class JavaTask implements Runnable{@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-----------JavaTask shutdown----------");}}public static void main(String[] args) {JavaTask javaTask=new JavaTask();Thread thread=new Thread(javaTask);thread.start();Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(4000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-----------JavaHook finish----------");}}));System.out.println("JVM finsih....");}
}

关闭钩子应该是线程安全的:他们在访问共享数据的时候必须使用同步机制,并且小心的避免发生死锁,这与其他并发代码的要求相同。而且关闭钩子不应该对应用程序的状态(比如其他服务是否已经关闭,后者所有的正常线是否已经执行完成)后者JVm的关闭原因作出任何假设。

5.总结

Java并没有提供某种抢占式的机制来取消或者终结线程。想法它提供一种协作式的中断机制来实现取消操作,但是这要依赖于如何构建取消操作的协议,以及能否遵循这些协议。

Java高并发编程:取消和关闭相关推荐

  1. Java高并发编程学习(三)java.util.concurrent包

    简介 我们已经学习了形成Java并发程序设计基础的底层构建块,但对于实际编程来说,应该尽可能远离底层结构.使用由并发处理的专业人士实现的较高层次的结构要方便得多.要安全得多.例如,对于许多线程问题,可 ...

  2. Java高并发编程详解系列-Java线程入门

    根据自己学的知识加上从各个网站上收集的资料分享一下关于java高并发编程的知识点.对于代码示例会以Maven工程的形式分享到个人的GitHub上面.   首先介绍一下这个系列的东西是什么,这个系列自己 ...

  3. Java高并发编程 (马士兵老师视频)笔记(一)同步器

    本篇主要总结同步器的相关例子:包括synchronized.volatile.原子变量类(AtomicXxx).CountDownLatch.ReentrantLock和ThreadLocal.还涉及 ...

  4. 29W 字总结阿里 Java 高并发编程:案例 + 源码 + 面试 + 系统架构设计

    下半年的跳槽季已经开始,好多同学已经拿到了不错的 Offer,同时还有一些同学对于 Java 高并发编程还缺少一些深入的理解,不过不用慌,今天老师分享的这份 27W 字的阿里巴巴 Java 高并发编程 ...

  5. 高并发编程-Thread_正确关闭线程的三种方式

    文章目录 概述 stop() Deprecated 方式一 设置开关 方式二 调用interrupt API 方式三 暴力结束线程-> Daemon Thread + interrupt API ...

  6. @冰河老师的巨作,人手一册的Java高并发编程指南,值得了解一下

    还真不好意思,这次 Java Thread Pool 惊爆了! 高并发是每一个后端开发工程师都头疼的问题,也是工程师能力的分水岭.要想基于JDK核心技术,玩转高并发编程,必须要好好修炼内功才行. 文章 ...

  7. Java高并发编程:活跃性危险

    Java高并发程序中,不得不出现资源竞争以及一些其他严重的问题,比如死锁.线程饥饿.响应性问题和活锁问题.在安全性与活跃性之间通常存在依赖,我们使用加锁机制来确保线程安全,但是如果过度地使用加锁,则可 ...

  8. Java高并发编程详解系列-7种单例模式

    引言 在之前的文章中从技术以及源代码的层面上分析了关于Java高并发的解决方式.这篇博客主要介绍关于单例设计模式.关于单例设计模式大家应该不会陌生,作为GoF23中设计模式中最为基础的设计模式,实现起 ...

  9. Java高并发编程(八):Java并发容器和框架

    1. ConcurrentHashMap 1.1 ConcurrentHashMap的优势 在并发编程中使用HashMap可能导致程序死循环.而使用线程安全的HashTable效率又非 常低下,基于以 ...

  10. Java高并发编程(二):Java并发机制的底层实现机制

    Java代码在编译后会变成Java字节码,字节码在之后被类加载机制加载到JVM中,JVM执行字节码,最终需要转换为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令. ...

最新文章

  1. Java5线程并发库之保障变量的原子性操作
  2. CSS学习笔记--浮动元素由于浏览器页面缩小而被挤到下面的解决方法
  3. linux cpu平均利用率st,理解 CPU 利用率
  4. 计算机考研没奖,备战考研本科期间没有什么奖项,考研复试会有影响吗?
  5. php redis存储位置,redis数据保存在哪里
  6. fusion按照多个centos,设置静态ip
  7. LeetCode1139. 最大的以 1 为边界的正方形 (二维滑动窗口待优化)
  8. 如何创建支持不同屏幕尺寸的Android应用(转载)
  9. python第七章_Python第七章
  10. k8s nfs安装及pv/pvc 创建和回收删除
  11. 用eclipse建立简单WebService客户端,使用WSDL,用于短信接口发送
  12. html超链接 鼠标效果,7种鼠标滑过超链接动画特效
  13. 关于STM8L系列低功耗井盖板设计记录【云南昆明电子设计开发工程师】
  14. React-Native-版高仿淘宝、京东商城首页、商品分类页面,android插件化和组件化
  15. input/output is not in graph tf.layers.conv2d在name命名时会自动在其后添加Conv2D
  16. 企业大楼AI无感考勤解决方案
  17. 国际短信平台接口调用的方法步骤,简单5步快速教程
  18. 数数字(UVa1225)
  19. 2021湖南涟源高考成绩查询,2021娄底市地区高考成绩排名查询,娄底市高考各高中成绩喜报榜单...
  20. 什么是大数据4v 指的是哪四个

热门文章

  1. [转]Java中Runtime.exec的一些事
  2. MarkdownPad 2 常用快捷键
  3. 操作系统——存储管理:分区、分页、分段、请求式分页和虚拟内存
  4. fps 每秒刷新的频率
  5. .NET C#语言基础 20140814
  6. Android 获取存储卡路径和空间使用情况
  7. Javascript 获取页面高度(多种浏览器)【转】
  8. OpenCV 4.2.0 发布,Intel 开源的计算机视觉库
  9. oracle最新scn补丁,更新:Oracle全面修正了关于DB Link和SCN补丁的公告
  10. mysql 全值匹配什么意思