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接口 所有的线程对象都 ...
随机推荐
- 深入理解hadoop之mapreduce
本文系原创,若有转载需要,请注明出处.https://www.cnblogs.com/bigdata-stone/ 1.mapReduce简介 MapReduce是面向大数据并行处理的计算模型.框架和 ...
- JS-闭包练习
首先,第一个输出,因为前置运算,i要先参与输出,然后再自增,所以输出为0 第二个输出,因为f1和f2是不同的函数,不共享i变量,所以输出也为0 第三个输出,因为是f1,共享i,所以i加了1,输出为1 ...
- 微信小程序编译提示tabar.iconPath 文件不存在
tabBar.list[0].iconPath 文件不存在 明明是按路径放了本地图片的,却依然显示路径不存在 需要把路径的图片转移到编译后的weapp文件中相同路径下的img文件中 本地正常路径 粘贴 ...
- django 文件上传样例以及遇到的一些问题
使用django上传文件 主流有两种方法 from表单以及ajax,为了自由度高一点,选择了ajax来实现文件的上传 前端部分代码如下: 主要关注 一 有一个文件上传(type='file')的按钮, ...
- beego从入门到弃坑(一)
最近由于要写课程设计的原因,我便开始一边学习beego,一边开始用它写一个小型的管理系统.但是只有你真正的去用的时候,才会发现这个框架巨坑,他是第一个让我写出了心里阴影的框架,也是第一个让我写着写 ...
- ThinkPHP模板继承和修改title
先说下模板继承: 假定:在View文件夹中 -> Public 公共模块 —>base/header/top/footer 4个html文件 这下面base文件使用include引入其他 ...
- TCP的keepalive和应用层的heart
从长链接说起 TCP是长链接的,也就是说连接建立后,及时数年没有通信连接仍然存在.这样做的好处是:免去了DNS解析的时间,连接建立等时间,大大加快了请求的速度,同时也有利于接受服务器的实时消息.但前提 ...
- Django的配置模板路径
Django的配置模板路径 找到settings.py 配置静态目录: 注:创建静态文件名就用static 不要用别的. 两个函数. return redirect ('http//:www.b ...
- fnmatch:Unix式glob模式匹配,简单场景下可以代替正则
介绍 fnmatch模块用于根据glob模式(如Unix shell所使用的的模式)比较文件名 简单匹配 import fnmatch ''' fnmatch将一个文件名与一个模式进行比较,并返回一个 ...
- centos能进入命令行界面,进不了图形界面
在开机引导界面按“e”, 找到linux16开头的一行,定位到ro然后修改ro为rw,并添加:init=/sysroot/bin/sh 使用ctrl x进入安全模式. 使用命令:chroot /sys ...