Thread类中有一个已经废弃的 stop() 方法,它可以终止线程,但由于它不管三七二十一,直接终止线程,所以被废弃了。比如,当线程被停止后还需要进行一些善后操作(如,关闭外部资源),使用这个方法就无能为力了。可以通过线程中断来实现线程终止。

首先来看一下Java线程中断的一些内容:

  • Java平台为每个线程维护了一个布尔型的中断标记,可以通过下列方法获取该标记的值:

    • interrupt() 中断某个线程
    • isInterrupted() 返回该线程的中断标记
    • interrupted() 返回并重置该线程的中断标记(置为false)
  • 中断仅是发起线程对目标线程的一种请求,也就是说,目标线程对这种请求可以相应,也可以忽略。
  • Java标准库中与线程阻塞相关的方法对中断的相应方式都是抛出 InterruptedException 异常,并且按照惯例,抛出异常前都会重置中断标记为false,因此这些方法会清空线程的中断标记
  • Java标准库中与线程阻塞相关的方法在进行阻塞前会判断中断标记是否为true,为true则抛出异常;如果在阻塞后调用中断方法的话,那么JVM会设置该线程的中断标记,然后将该线程唤醒,因此中断具有唤醒线程的作用

由上面几点和第二句加粗的话可知,可以使用线程中断来实现线程终止,只要目标线程判断一下中断标记即可,即使被中断的线程正处于阻塞状态,也能把他唤醒起来终止;由第一句加粗的话可知,直接使用线程中断实现线程终止是存在风险的,因为可能调用了一些Java标准库的阻塞方法,而导致了中断标记被清空,也就无法获得中断标记了(总是false),因此需要自己创建一个中断标记配合使用。

如,下面是一个可中断的任务执行器,他会在每次执行任务前,判断一下自定i的终止标记和剩余的任务数(善后);提供的shutdown方法除了将工作线程中断外(主要作用是唤醒可能处于阻塞状态的任务),还会将终止交集 terminated 置为 true。

执行 main 方法,可以发现,首先会打印出“客户端调用了 shutdown 方法”,然后过了四秒,main线程才会终止,可知shutdown方法正确地将目标线程终止了。关于“按照惯例,Java标准库中抛出InterruptedException异常的和线程相关的阻塞方法会清空中断标记”,可以将条件中的 !interminated 替换成 !Thread.currentThread().isInterrupted(),然后再执行main方法测试,可以发现main线程始终无法终止,因为 sleep() 方法清空了中断标记,所以  !Thread.currentThread().isInterrupted() 始终为true,导致工作线程始终无法终止。

public class TerminableTaskRunner {
// 存储要执行的任务
private final BlockingQueue<Runnable> tasks;
// 线程终止标志
private volatile boolean terminated;
// 剩余的任务数
private final AtomicInteger count;
// 实际执行任务的线程
private volatile Thread workThread; public TerminableTaskRunner(int capacity) {
this.tasks = new LinkedBlockingDeque<>(capacity);
this.count = new AtomicInteger(0);
this.workThread = new WorkThread();
workThread.start();
} public void submit(Runnable task) {
this.tasks.add(task);
this.count.incrementAndGet();
} public void shutdown() {
terminated = true; // 线程终止标志,由于中断标志可能会被覆盖,所以需要自己创建一个标志
if (workThread != null)
workThread.interrupt(); // 唤醒线程
} private class WorkThread extends Thread {
@Override
public void run() {
Runnable task;
try {
while (!terminated || tasks.size() >= 1) {
task = tasks.take();
try {
task.run(); // 可能会清空当前线程的中断标记,如task.run()在内部调用的阻塞方法抛出了InterruptedException
} catch (Throwable e) {
e.printStackTrace();
}
count.decrementAndGet();
}
} catch (InterruptedException e) {
// 一旦调用shutdown且tasks.take()阻塞住,就抛出该异常,没有任务要执行,直接终止
workThread = null;
}
}
} public static void main(String[] args) {
TerminableTaskRunner taskRunner = new TerminableTaskRunner(4);
for (int i = 0; i < 4; i++) {
taskRunner.submit(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println("客户端调用了 shutdown 方法");
}
});
}
taskRunner.shutdown(); }
}

  

Java中正确终止线程的方法的更多相关文章

  1. Java中各种(类、方法、属性)访问修饰符与修饰符的说明

    类: 访问修饰符 修饰符 class 类名称 extends 父类名称 implement 接口名称 (访问修饰符与修饰符的位置可以互换) 访问修饰符 名称 说明 备注 public 可以被本项目的所 ...

  2. Java中的守护线程 & 非守护线程(简介)

    Java中的守护线程 & 非守护线程 守护线程 (Daemon Thread) 非守护线程,又称用户线程(User Thread) 用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守 ...

  3. Java中如何创建线程

    Java中如何创建线程 两种方式:1)继承Thread类:2)实现Runnable接口. 1.继承Thread类 继承Thread类,重写run方法,在run方法中定义需要执行的任务. class M ...

  4. Java中的栈,堆,方法区和常量池

    要说Java中的栈,堆,方法区和常量池就要提到HotSpot,HotSpot是Sun JDK 和 Open JDK中所带的虚拟机. (Sun JDK 和 Open JDK除了注释不同,代码实现基本上是 ...

  5. Java 中extends与implements使用方法

    Java 中extends与implements使用方法 标签: javaclassinterfacestring语言c 2011-04-14 14:57 33314人阅读 评论(7) 收藏 举报 分 ...

  6. Java中的equals和hashCode方法

    本文转载自:Java中的equals和hashCode方法详解 Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要 ...

  7. Java中替换HTML标签的方法代码

    这篇文章主要介绍了Java中替换HTML标签的方法代码,需要的朋友可以参考下 replaceAll("\\&[a-zA-Z]{0,9};", "").r ...

  8. java中需要关注的3大方面内容/Java中创建对象的几种方法:

    1)垃圾回收 2)内存管理 3)性能优化 Java中创建对象的几种方法: 1)使用new关键字,创建相应的对象 2)通过Class下面的new Instance创建相应的对象 3)使用I/O流读取相应 ...

  9. Java中字符串的一些常见方法

    1.Java中字符串的一些常见方法 /** * */ package com.you.model; /** * @author Administrator * @date 2014-02-24 */ ...

随机推荐

  1. 1091 N-自守数 (15 分)C语言

    如果某个数 K 的平方乘以 N 以后,结果的末尾几位数等于 K,那么就称这个数为"N-自守数".例如 3×92^​2​​ =25392,而 25392 的末尾两位正好是 92,所以 ...

  2. Spring中的ApplicationListener扩展使用

    ApplicationListener是Spring事件机制的一部分,与抽象类ApplicationEvent类配合来完成ApplicationContext的事件机制. 如果容器中存在Applica ...

  3. React Native的缓存和下载

    一般我们有3种数据需要缓存和下载:纯文本(比如API返回,状态标记等),图片缓存和其他静态文件. 纯文本 纯文本还是比较简单的,RN官方模块AsyncStorage就够了.它就跟HTML5里面的Loc ...

  4. 欧拉-拉格朗日方程 The Euler-Lagrange Equation

    在 paper: Bounded Biharmonic Weights for Real-Time Deformation 中第一次接触到 Euler-Lagrange 方程,简单记录一下. 泛函的定 ...

  5. TensorFlow——dropout和正则化的相关方法

    1.dropout dropout是一种常用的手段,用来防止过拟合的,dropout的意思是在训练过程中每次都随机选择一部分节点不要去学习,减少神经元的数量来降低模型的复杂度,同时增加模型的泛化能力. ...

  6. 第一篇博客-- 走上IT路

    首先介绍一下本人,我是一名在校大学生,在一次学长分享学习经验时了解到,写博客可以帮助复习.所以这就是我要写博客的原因. 我是非常喜欢网络安全技术,因此我选择了我这个专业.在接下来的一段时间我会在这里记 ...

  7. .net Core 使用IHttpClientFactory请求

            导读:本文已添加在晨曦微服务之旅,现在自己在尝试微服务架构,一边学边做项目快速的进入状态.当然在学习的过程中会将自己学到的知识进行分享. 一.为什么不用HttpClient       ...

  8. Kubernetes concepts 系列

    kubernetes concepts overview Pod overview Replication Controller Pod Liftcycle Termination Of Pod Re ...

  9. 关于Matplotlib中No module named 'matplotlib.finance'的解决办法

    最近在研究量化分析,需要用到matplotlib中的一个库,输入from matplotlib.finance import quotes_historical_yahoo_ohlc, candles ...

  10. 轻松理解 Kubernetes 的核心概念

    Kubernetes 迅速成为云环境中软件部署和管理的新标准. 与强大的功能相对应的是陡峭的学习曲线. 本文将提供 Kubernetes 的简化视图,从高处观察其中的重要组件,以及他们的关联. 硬件 ...