停止一个线程通常意味着在线程处理任务完成之前停掉正在做的操作,也就是放弃当前的操作。

在 Java 中有以下 3 种方法可以终止正在运行的线程:

  1. 使用退出标志,使线程正常退出,也就是当 run() 方法完成后线程中止。
  2. 使用 stop() 方法强行终止线程,但是不推荐使用这个方法,该方法已被弃用。
  3. 使用 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:

  1. 调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。
  2. 调用 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终止线程的三种方式的更多相关文章

  1. Java: Java终止线程的几种方式

    首先说明,使用stop方法终止的方式已经在很久之前就被废弃了,在加锁的情况下有可能会造成死锁,这里不做讨论. 1. 使用标志位终止线程 在run()方法执行完毕后,该线程就终止了.但是在某些特殊的情况 ...

  2. Java实现线程的三种方式和区别

    Java实现线程的三种方式和区别 Java实现线程的三种方式: 继承Thread 实现Runnable接口 实现Callable接口 区别: 第一种方式继承Thread就不能继承其他类了,后面两种可以 ...

  3. Java并发编程:Java创建线程的三种方式

    目录 引言 创建线程的三种方式 一.继承Thread类 二.实现Runnable接口 三.使用Callable和Future创建线程 三种方式的对比 引言 在日常开发工作中,多线程开发可以说是必备技能 ...

  4. java创建线程的三种方式及其对比

    第一种方法:继承Thread类,重写run()方法,run()方法代表线程要执行的任务.第二种方法:实现Runnable接口,重写run()方法,run()方法代表线程要执行的任务.第三种方法:实现c ...

  5. AJPFX总结java创建线程的三种方式及其对比

    Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行 ...

  6. java创建线程的三种方式及其对照

    Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类.并重写该类的run方法,该run方法的方法体就代表了线程要完毕的任务.因此把run()方法称为运行 ...

  7. java中终止线程的三种方式

    在java中有三种方式可以终止线程.分别为: 1.  使用退出标志,使线程正常退出,也就是当run方法完成后线程终止.  2.  使用stop方法强行终止线程(这个方法不推荐使用,因为stop和sus ...

  8. Java创建线程的三种方式

    一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行体. (2)创建Thread子类的实 ...

  9. 0036 Java学习笔记-多线程-创建线程的三种方式

    创建线程 创建线程的三种方式: 继承java.lang.Thread 实现java.lang.Runnable接口 实现java.util.concurrent.Callable接口 所有的线程对象都 ...

随机推荐

  1. 设计模式风格<二>;消息总线

    以前开发的动车模拟驾驶系统,有好几个软件(不在一台机器上),他们互相之间通信,因此每个软件要配置每个模块的IP和端口,就是每个模块都要知道别的模块的端口和IP. 这样有个重复的地方,B模块和C模块都要 ...

  2. CN丶Moti-个人博客

    欢迎访问我的个人博客,获取更多有用的东西 链接一 链接二 也可以关注我的微信订阅号:CN丶Moti

  3. php 如何将image图片转化为字符串(GD库操作及imagick两种实现方式)

    前两天研究php中的 imagick 扩展的时候,突发奇想实现的一个小功能感觉挺有意思,在这里记录一下: 将一张image图片转化为字符串的形式,先上一张效果图.(运行笔记中的代码需要先安装 php_ ...

  4. pycharm 报错 ModuleNotFoundError: No module named 'distutils.core'

    之气的虚拟机不好使了,重新装了最新版本,安装了ubuntu18.4,然后安装pycharm,但新建项目的时候报错: ModuleNotFoundError: No module named 'dist ...

  5. 【小知识点】去除inline-block元素间间距的办法

    之前一直用float浮动方法布局,因为display:inline-block有间隙,现在找到办法了.在父元素上面加font-sise:0,就可以了. 效果如图: 代码如下: <!DOCTYPE ...

  6. 切记:永远不要在MySQL中使用UTF-8

    MySQL使用UTF-8 issue 问题描述:最近我遇到了一个bug,我试着通过Rails在以“utf8”编码的MariaDB中保存一个UTF-8字符串,然后出现了一个离奇的错误: Incorrec ...

  7. Hadoop_20_MapReduce程序的运行模式

    1.MapReduce程序的运行模式 1. Windows中运行MapReduce程序 (1)mapreduce程序是被提交给LocalJobRunner在本地以单进程的形式运行 (2)而处理的数据及 ...

  8. 基于C++11的100行实现简单线程池

    基于C++11的100行实现简单线程池 1 线程池原理 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小, ...

  9. 7. Function Decorators and Closures

    A decorator is a callable that takes another function as argument (the decorated function). The deco ...

  10. Go测试开发就用这三板斧

    一个古老的面试问题:“给你个XX,你怎么测试?” 时间穿越到9102,Go语言成为了新生代的代名词.老问题变成了“给你Golang程序,你怎么测试?”     看完本文后,读者可以拍着胸脯回答,“一共 ...