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. 2013 ACM-ICPC亚洲区域赛南京站C题 题解 轮廓线DP

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4804 题目大意 给你一个 \(n \times m\) 的矩形区域.你需要用 \(1 \times 1 ...

  2. 极光推送SDK通过泰尔终端实验室检测,符合统一推送接口标准

    1月7日,中国深圳--国内领先的开发者服务提供商极光(Aurora Mobile, NASDAQ:JG)宣布其旗下产品极光推送SDK通过中国信息通信研究院泰尔终端实验室的检测,其性能和接口标准符合统一 ...

  3. 如何在Go项目中输出版本信息?

    我们经常在使用CLI工具的时候,都会有这样的参数输出: ``` ➜ ~ docker version Client: Docker Engine - Community Version: 18.09. ...

  4. 应届生/社招面试最爱问的几道Java基础问题

    本文已经收录自笔者开源的 JavaGuide: https://github.com/Snailclimb ([Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识)如果觉得不错 ...

  5. Http GetPost网络请求

    using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System ...

  6. InteliJ IDEA 启动应用出现 Error:failed to create a child event loop 解决方案

    1.首先检查自己电脑是否能够联网 2.打开电脑防火墙允许 InteliJ IDEA 进行通信如下图所示

  7. Scala实践5

    一.Scala的层级 1.1类层级 Scala中,Any是所其他类的超类,在底端定义了一些有趣的类NULL和Nothing,是所有其他类的子类. 根类Any有两个子类:AnyVal和AnyRef.其中 ...

  8. 重拾c++第四天(7):函数相关

    1.引用变量: int a; int &b = a; //引用变量 指向同一地址,必须在初始化时定义,且一直对原变量献上忠诚,主要针对类对象 2.函数重载最好用在功能相同,但数据类型不同的情况 ...

  9. 重拾c++第一天(1):环境配置

    时过多年,c++基本不记得了,故在此记录相关重拾记录. 学习语言第一步当然是环境配置了(笑),由于暂无用c++进行大型项目开发的需求,所以先下载dev进行过渡. 安装过程非常简单,值得注意的是配置时选 ...

  10. .net Core 使用IHttpClientFactory请求

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