如何停止一个正在运行的线程

1. 前言

停止一个线程更意味着在任务处理完成任务之前停掉正在做的操作,也就是放弃当前的操作。停止一个线程可以用Thread.stop()方法,但最好不要用它。虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且已被废弃。

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

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止

  2. 使用stop方法强行终止,但是不推荐;因为stop和suspend以及resume一样都是过期作废的方法

  3. 使用interrupt方法中断线程

2. 停止不了的线程

interrupt()方法的使用效果并不像for+break语句那样,马上就停止循环,调用interrupt()方法是在当前线程中打一个停止标志,并不是真的停止线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ThreadDemo extends Thread {
@Override
public void run() {
super.run();
for (int i = 0; i < 100000; i++) {
System.out.println("i="+(i+1));
}
}
}

public class TestThreadDemo {
public static void main(String[] args) {
Thread thread = new ThreadDemo();
thread.start();
try{
//这里休眠2秒,让线程执行一段时间,2s后中断线程
Thread.sleep(2000);
thread.interrupt();
}catch (Exception ex){
ex.printStackTrace();
}
}
}

输出结果:

1
2
3
4
5
6
7
8
...
i=99994
i=99995
i=99996
i=99997
i=99998
i=99999
i=100000

3. 判断线程是否停止状态

Thread.java类提供了两种方法:

  1. this.interrupted():测试当前线程是否已经中断,当前线程是指运行this.interrput()方法的线程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class ThreadDemo extends Thread {

    @Override
    public void run() {
    super.run();
    for (int i = 0; i < 1000000; i++) {
    i++;
    }
    }
    }
    public class TestThreadDemo {
    public static void main(String[] args) {
    Thread thread = new ThreadDemo();
    thread.start();
    try {
    Thread.sleep(2000);
    thread.interrupt();

    System.out.println("stop 1??" + Thread.interrupted());
    System.out.println("stop 2??" + Thread.interrupted());
    } catch (Exception ex) {
    ex.printStackTrace();
    }
    }
    }

    运行结果:

    1
    2
    stop 1??false
    stop 2??false

    从控制台输出信息可以看出:线程并未停止,这也证明来interrupt()方法的解释,测试当前线程是否已经中断,这个当前线程是main,它未被中断,所以打印的两个结果都是false;

    如何使main线程产生中断效果呢?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class TestThreadDemo2 {
    public static void main(String[] args) {
    Thread.currentThread().interrupt();
    System.out.println("stop 1??" + Thread.interrupted());
    System.out.println("stop 2??" + Thread.interrupted());

    System.out.println("end");
    }
    }

    输出:

    1
    2
    3
    stop 1??true
    stop 2??false
    end

    方法interrupt()的确判断出当前线程是否停止状态,但是为什么第2个为false呢?

    官方帮助文档对interrupt()方法的解释是:测试当前线程是否已经中断。线程的中断状态有该方法清除。换句话说:如果连续两次调用该方法,则第二次返回false;

  2. this.isInterrupted():测试线程是否已经中断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class IsInterruptDemo {
    public static void main(String[] args) {
    Thread thread = new ThreadDemo();
    thread.start();
    thread.interrupt();
    System.out.println("stop 1??" + thread.isInterrupted());
    System.out.println("stop 2??" + thread.isInterrupted());
    }
    }

    运行结果:

    1
    2
    stop 1??true
    stop 2??true

    isInterrupted()并为清除状态,所以打印来两个true

4. 停止线程-异常法

有了前面的知识,就可以在线程中用for来判断线程是否是停止状态,如果是停止状态,则后面的代卖不再运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ThreadDemo extends Thread {

@Override
public void run() {
super.run();
for (int i = 0; i < 500000; i++) {
if (Thread.interrupted()) {
System.out.println("线程已经终止,for循环不再执行");
break;
}
System.out.println("i = " + (i + 1));
}
System.out.println("这是for循环外的语句,也会被执行");
}
}
public class TestThreadDemo {
public static void main(String[] args) {
Thread thread = new ThreadDemo();
thread.start();
try {
Thread.sleep(1000);
thread.interrupt();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

运行结果:

1
2
3
4
5
6
7
8
......
i = 285135
i = 285136
i = 285137
i = 285138
i = 285139
线程已经终止,for循环不再执行
这是for循环外的语句,也会被执行

虽然停止了线程,但是如果for语句下面还有语句,还是会继续执行的。如果不需要继续执行

只需要在上面break的地方改成throw new InterruptedException(); 即可,这样就会抛出异常。

5. 停止线程-sleep法

如果线程在sleep状态下停止线程,会是什么效果?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ThreadDemo extends Thread {

@Override
public void run() {
super.run();
try {
System.out.println("线程开始....");
Thread.sleep(200000);
System.out.println("线程结束。");
} catch (InterruptedException e) {
System.out.println("在沉睡中被停止, 进入catch, 调用isInterrupted()方法的结果是:"+this.isInterrupted());
e.printStackTrace();
}
}
}
public class TestThreadDemo {
public static void main(String[] args) {
Thread thread = new ThreadDemo();
thread.start();
try {
thread.interrupt();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

运行结果:

1
2
3
4
5
线程开始....
在沉睡中被停止, 进入catch, 调用isInterrupted()方法的结果是:false
java.lang.InterruptedException: sleep interrupted
trueat java.lang.Thread.sleep(Native Method)
trueat org.example.ThreadDemo.run(ThreadDemo.java:13)

从打印的结果来看,如果在sleep状态下停止线程,会进入catch语句,并且清除停止状态值,使之变为false。

前一个实验是先sleep然后再用interrupt()挺尸,与之相反的操作如下:

1
2
3
4
5
6
7
8
9
.......
i=9997
i=9998
i=9999
i=10000
先停止,再遇到sleep 进入catch
java.lang.InterruptedException: sleep interrupted
trueat java.lang.Thread.sleep(Native Method)
trueat org.example.ThreadDemo.run(ThreadDemo.java:16)

6. 停止线程-暴力停止

使用stop()方法停止线程是非常暴力的,且方法已经被废弃了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class ThreadDemo extends Thread {
private int i = 0;
@Override
public void run() {
super.run();
try {
//模拟线程一直在运行
while (true) {
System.out.println("i = " + i);
i++;
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TestThreadDemo {
public static void main(String[] args) {
Thread thread = new ThreadDemo();
thread.start();
try{
Thread.sleep(2000);
thread.stop();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9

Process finished with exit code 0

7. 方法stop()与java.lang.ThreadDeath异常

调用stop()方法时会抛出java.lang.ThreadDeath异常,但是通常情况下,此异常不需要显示地捕捉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ThreadDemo extends Thread {
@Override
public void run() {
super.run();
try {
this.stop();
} catch (ThreadDeath e) {
System.out.println("进入catch");
e.printStackTrace();
}
}
}
public class TestThreadDemo {
public static void main(String[] args) {
Thread thread = new ThreadDemo();
thread.start();
}
}

运行结果:

1
2
3
4
进入catch
java.lang.ThreadDeath
trueat java.lang.Thread.stop(Thread.java:853)
trueat org.example.ThreadDemo.run(ThreadDemo.java:11)

stop()方法已经作废,因为如果强制让线程停止有可能是一些清理性的工作得不到完整,另一种情况就是对锁定的对象进行了解锁,导致数据得不到同步的处理,出现数据不一致的情况。

释放锁的不良后果

使用stop()释放锁将会给数据造成不一致性的结果。如果出现这样的情况,程序处理的数据就有可能遭到破坏,最终导致程序执行的流程错误,一定要特别注意:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class SynchronizedObject {
private String name = "a";
private String password = "aa";

public synchronized void printString(String name, String password){
try {
this.name = name;
Thread.sleep(100000);
this.password = password;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//省略getter/setter方法
}

public class MyThread extends Thread {
private SynchronizedObject synchronizedObject;
public MyThread(SynchronizedObject synchronizedObject){
this.synchronizedObject = synchronizedObject;
}

public void run(){
synchronizedObject.printString("b", "bb");
}
}

public class TestThreadDemo {
public static void main(String args[]) throws InterruptedException {
SynchronizedObject synchronizedObject = new SynchronizedObject();
Thread thread = new MyThread(synchronizedObject);
thread.start();
Thread.sleep(500);
thread.stop();
System.out.println(synchronizedObject.getName() + " " + synchronizedObject.getPassword());
}
}

输出结果:

1
b  aa

由于stop()方法以及在JDK中被标明为“过期/作废”的方法,显然它在功能上具有缺陷,所以不建议在程序张使用stop()方法。

8. 使用return停止线程

将方法interrupt()与return结合使用也能实现停止线程的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ThreadDemo extends Thread {
@Override
public void run() {
while (true) {
if (this.isInterrupted()) {
System.out.println("线程被停止了");
return;
}
System.out.println("time:" + System.currentTimeMillis());
}
}
}
public class TestThreadDemo {
public static void main(String[] args) {
Thread thread = new ThreadDemo();
thread.start();
try {
Thread.sleep(2000);
thread.interrupt();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

输出结果:

1
2
3
4
5
6
7
.......
time:1676209667113
time:1676209667113
time:1676209667113
time:1676209667113
time:1676209667113
线程被停止了

总结:建议使用“抛异常”的方法来实现线程的停止,因为在catch块中还可以将异常向上抛,使线程停止事件得以传播