Java终止线程的三种方式
停止一个线程通常意味着在线程处理任务完成之前停掉正在做的操作,也就是放弃当前的操作。
在 Java 中有以下 3 种方法可以终止正在运行的线程:
- 使用退出标志,使线程正常退出,也就是当 run() 方法完成后线程中止。
- 使用 stop() 方法强行终止线程,但是不推荐使用这个方法,该方法已被弃用。
- 使用 interrupt 方法中断线程。
1. 使用标志位终止线程
在 run() 方法执行完毕后,该线程就终止了。但是在某些特殊的情况下,run() 方法会被一直执行;比如在服务端程序中可能会使用 while(true) { ... }
这样的循环结构来不断的接收来自客户端的请求。此时就可以用修改标志位的方式来结束 run() 方法。
public class ServerThread extends Thread {
//volatile修饰符用来保证其它线程读取的总是该变量的最新的值
public volatile boolean exit = false;
@Override
public void run() {
ServerSocket serverSocket = new ServerSocket(8080);
while(!exit){
serverSocket.accept(); //阻塞等待客户端消息
...
}
}
public static void main(String[] args) {
ServerThread t = new ServerThread();
t.start();
...
t.exit = true; //修改标志位,退出线程
}
}
2. 使用 stop() 终止线程
通过查看 JDK 的 API,我们会看到 java.lang.Thread 类型提供了一系列的方法如 start()、stop()、resume()、suspend()、destory()等方法来管理线程。但是除了 start() 之外,其它几个方法都被声名为已过时(deprecated)。
虽然 stop() 方法确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且该方法已被弃用,最好不要使用它。
JDK 文档中还引入用一篇文章来解释了弃用这些方法的原因:《Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?》
为什么弃用stop:
- 调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。
- 调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。
例如,存在一个对象 u 持有 ID 和 NAME 两个字段,假如写入线程在写对象的过程中,只完成了对 ID 的赋值,但没来得及为 NAME 赋值,就被 stop() 导致锁被释放,那么当读取线程得到锁之后再去读取对象 u 的 ID 和 Name 时,就会出现数据不一致的问题,如下图:
3. 使用 interrupt() 中断线程
现在我们知道了使用 stop() 方式停止线程是非常不安全的方式,那么我们应该使用什么方法来停止线程呢?答案就是使用 interrupt() 方法来中断线程。
需要明确的一点的是:interrupt() 方法并不像在 for 循环语句中使用 break 语句那样干脆,马上就停止循环。调用 interrupt() 方法仅仅是在当前线程中打一个停止的标记,并不是真的停止线程。
也就是说,线程中断并不会立即终止线程,而是通知目标线程,有人希望你终止。至于目标线程收到通知后会如何处理,则完全由目标线程自行决定。这一点很重要,如果中断后,线程立即无条件退出,那么我们又会遇到 stop() 方法的老问题。
事实上,如果一个线程不能被 interrupt,那么 stop 方法也不会起作用。
我们来看一个使用 interrupt() 的例子:
public class InterruptThread1 extends Thread{
public static void main(String[] args) {
try {
InterruptThread1 t = new InterruptThread1();
t.start();
Thread.sleep(200);
t.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
super.run();
for(int i = 0; i <= 200000; i++) {
System.out.println("i=" + i);
}
}
}
输出:
从输出的结果我们会发现 interrupt 方法并没有停止线程 t 中的处理逻辑,也就是说即使 t 线程被设置为了中断状态,但是这个中断并不会起作用,那么该如何停止线程呢?
这就需要使用到另外两个与线程中断有关的方法了:
public boolean Thread.isInterrupted() //判断是否被中断
public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态
这两个方法使得当前线程能够感知到是否被中断了(通过检查标志位)。
所以如果希望线程 t 在中断后停止,就必须先判断是否被中断,并为它增加相应的中断处理代码:
@Override
public void run() {
super.run();
for(int i = 0; i <= 200000; i++) {
//判断是否被中断
if(Thread.currentThread().isInterrupted()){
//处理中断逻辑
break;
}
System.out.println("i=" + i);
}
}
输出结果,for 循环在执行完成前就提前结束了:
在上面这段代码中,我们增加了 Thread.isInterrupted() 来判断当前线程是否被中断了,如果是,则退出 for 循环,结束线程。
这种方式看起来与之前介绍的“使用标志位终止线程”非常类似,但是在遇到 sleep() 或者 wait() 这样的操作,我们只能通过中断来处理了。
public static native void sleep(long millis) throws InterruptedException
Thread.sleep() 方法会抛出一个 InterruptedException 异常,当线程被 sleep() 休眠时,如果被中断,这会就抛出这个异常。
(注意:Thread.sleep() 方法由于中断而抛出的异常,是会清除中断标记的。)
Java终止线程的三种方式的更多相关文章
- Java: Java终止线程的几种方式
首先说明,使用stop方法终止的方式已经在很久之前就被废弃了,在加锁的情况下有可能会造成死锁,这里不做讨论. 1. 使用标志位终止线程 在run()方法执行完毕后,该线程就终止了.但是在某些特殊的情况 ...
- Java实现线程的三种方式和区别
Java实现线程的三种方式和区别 Java实现线程的三种方式: 继承Thread 实现Runnable接口 实现Callable接口 区别: 第一种方式继承Thread就不能继承其他类了,后面两种可以 ...
- Java并发编程:Java创建线程的三种方式
目录 引言 创建线程的三种方式 一.继承Thread类 二.实现Runnable接口 三.使用Callable和Future创建线程 三种方式的对比 引言 在日常开发工作中,多线程开发可以说是必备技能 ...
- java创建线程的三种方式及其对比
第一种方法:继承Thread类,重写run()方法,run()方法代表线程要执行的任务.第二种方法:实现Runnable接口,重写run()方法,run()方法代表线程要执行的任务.第三种方法:实现c ...
- AJPFX总结java创建线程的三种方式及其对比
Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行 ...
- java创建线程的三种方式及其对照
Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类.并重写该类的run方法,该run方法的方法体就代表了线程要完毕的任务.因此把run()方法称为运行 ...
- java中终止线程的三种方式
在java中有三种方式可以终止线程.分别为: 1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止. 2. 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和sus ...
- Java创建线程的三种方式
一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行体. (2)创建Thread子类的实 ...
- 0036 Java学习笔记-多线程-创建线程的三种方式
创建线程 创建线程的三种方式: 继承java.lang.Thread 实现java.lang.Runnable接口 实现java.util.concurrent.Callable接口 所有的线程对象都 ...
随机推荐
- JS基础_对象的方法
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- python之如何爬取一篇小说的第一章内容
现在网上有很多小说网站,但其实,有一些小说网站是没有自己的资源的,那么这些资源是从哪里来的呢?当然是“偷取”别人的数据咯.现在的问题就是,该怎么去爬取别人的资源呢,这里便从简单的开始,爬取一篇小说的第 ...
- react hooks学习
接触React项目快两个月了,还在研究摸索各种知识点的过程中,充实且幸福. 在项目中学习新知识,还是很有效率的,一边写项目,一边实验新的知识点,比如react hooks!嘻嘻嘻~~~ 写了好一段时间 ...
- python异步编程 (转载)
Python Async/Await入门指南 转自:https://zhuanlan.zhihu.com/p/27258289 本文将会讲述Python 3.5之后出现的async/await的使 ...
- C++获取文件夹下所有文件的路径
代码 getFiles()函数的作用: path是一个文件夹路径,函数在path文件夹下寻找所有文件(包括子文件夹下的文件),然后将所有文件的路径存入files #include <io.h&g ...
- PAT乙级1038
题目链接 https://pintia.cn/problem-sets/994805260223102976/problems/994805284092887040 题解一 这份代码最后一个点会超时 ...
- golang restful api
https://medium.com/@petrousov/how-to-build-a-restful-api-in-go-for-phonebook-app-d55f7234a10 ------- ...
- Boosting算法(一)
本章全部来自于李航的<统计学>以及他的博客和自己试验.仅供个人复习使用. Boosting算法通过改变训练样本的权重,学习多个分类器,并将这些分类器进行线性组合,提高分类性能.我们以Ada ...
- python打开excel跳转对应分页
需求: 在python脚本层,直接打开excel,跳转特定分页,方便策划编辑 尝试了几种不同的方法,最终选择了方法4.4种方法都可以实现打开外部文件,3.4可以实现跳转特定分页.3和4的区别是,3通过 ...
- mysql数据库系统学习(一)---一条SQL查询语句是如何执行的?
本文基于----MySQL实战45讲(极客时间----林晓斌 )整理----->https://time.geekbang.org/column/article/68319 一.第一节:一条sq ...