停止线程

停止线程是在多线程开发时很重要的技术点,掌握此技术可以对线程的停止进行有效的处理。

停止不了的线程

调用interrupt()方法来停止线程,但interrupt()方法的使用效果并不像for-break语句那样,马上就停止循环。调用interrupt()方法仅仅是当前线程中打了一个停止的标记,并不是真正的停止线程
代码清单1 interrupt()方法没有真正停止线程

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 InterruptThread extends Thread {
@Override
public void run() {
super.run();
for(int i=0;i<500000;i++){
System.out.println("i="+(i+1));
}
}
}
public class InterruptRun {
public static void main(String[] args) {
try {
InterruptThread thread=new InterruptThread();
thread.start();
Thread.sleep(2000);
thread.interrupt();
} catch (InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
}
}

从运行结果看,调用interrupt方法并没有停止线程。如何停止线程呢?



图1 控制台输出50万行的日志

判断线程是否是停止状态

判断线程是不是停止,Thread.java类里提供了两种方法。
1)this.interrupted():测试当前线程是否已经中断,执行后具有将状态标志置清楚为false()的功能。其声明为:

1
2
3
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}

2)this.isInterrupted():测试线程Thread对象是否已经中断,但不清楚状态标志。其声明为:

1
2
3
public boolean isInterrupted() {
return isInterrupted(false);
}

而isInterrupted(Boolean ClearInterrupted)是一个本地方法,private native boolean isInterrupted(boolean ClearInterrupted);

那么这两个方法有什么区别呢?
a. this.interrupted()方法的解释就是测试当前的线程是否中断,当前线程是指运行this.interrupted()方法的线程。
代码清单2 测试interrupted()方法

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
public class InterruptThread extends Thread {
@Override
public void run() {
super.run();
for(int i=0;i<500000;i++){
System.out.println("i="+(i+1));
}
}
}
public class InterruptedRun1 {
public static void main(String[] args) {
try {
InterruptThread thread=new InterruptThread();
thread.start();
Thread.sleep(2000);
thread.interrupt(); //停止thread对象所代表的线程
//Thread.currentThread().interrupt();
/*
下面的输出是来判断thread对象所代表的线程是否停止,但从控制台打印的结果来看,
线程并未停止,这也证明了interrupted()方法的解释:测试当前线程是否已经中断。
这个“当前线程是main”,它从未中断过,所以打印的结果是两个false。
*/
System.out.println("是否停止1?="+thread.interrupted());
System.out.println("是否停止2?="+thread.interrupted());
} catch (InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
}
}



图2 thread.interrupted()代表mian线程

b. 如何使main线程产生中断效果呢?
代码清单3 mian线程中断

1
2
3
4
5
6
7
8
9
public class InterruptedRun2 {
public static void main(String[] args) {
Thread.currentThread().interrupt();
System.out.println("是否停止1? = "+Thread.interrupted());
System.out.println("是否停止2? = "+Thread.interrupted());
System.out.println("end!");
}
}



图3 主线程mian已是停止

从上述结果看,方法interrupted()的确判断出当前线程是否停止状态。但为什么第二个布尔值是false呢?查看一下官方帮助文档对interrupted方法的解释。
测试当前线程是否中断。线程的中断状态由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
文档已经解释很详细,interrupted()方法具有清除状态的功能,所以第2次调用interrupted()方法返回的值是false。

c. 介绍完interrupted()方法后再来看一下isInterrupted()方法。
代码清单4 isInterrupted()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class InterruptedRun3 {
public static void main(String[] args) {
try {
InterruptThread thread=new InterruptThread();
thread.start();
Thread.sleep(1000);
thread.interrupt();
//Thread.currentThread().interrupt();
System.out.println("是否停止1?="+thread.isInterrupted());
System.out.println("是否停止2?="+thread.isInterrupted());
} catch (InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
}
}

从结果中可以看到,方法isInterrupted()并未清除状态标志,所以打印两个true。



图4 已经是停止状态

能停止的线程–异常法

1) 在线程中用for语句来判断,线程是否是停止状态,如果是停止状态,则后面的代码不再运行即可。
代码清单5 break停止线程

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 static void main(String[] args) {
try {
ErrorInterruptThread thread = new ErrorInterruptThread();
thread.start();
Thread.sleep(2000);
thread.interrupt();
} catch (InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
System.out.println("end!");
}
}
public class ErrorInterruptThread extends Thread {
@Override
public void run() {
super.run();
for(int i=0;i<500000;i++){
if(this.interrupted()){
System.out.println("已经是停止状态了!我要退出!");
break;
}
System.out.println("i="+(i+1));
}
// System.out.println("我被输出,如果此代码时for又继续运行,线程并未停止!");
}
}



图5 线程已经是停止状态

2) 上面的示例虽然停止了线程,但如果for语句下面还有语句,还是会继续运行的。
代码清单5中,去掉System.out.println("我被输出,如果此代码时for又继续运行,线程并未停止!");的注释。运行结果如图6所示。



图6 for后面的语句继续运行

3) 对于语句继续运行的问题,该怎么解决呢?
建议使用该方法来实现线程停止!
代码清单6 抛出错误InterruptedException

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
38
public class ErrorInterruptThread2 extends Thread {
@Override
public void run() {
super.run();
try {
for (int i = 0; i < 500000; i++) {
if (this.interrupted()) {
System.out.println("已经是停止状态了!我要退出!");
throw new InterruptedException();
}
System.out.println("i=" + (i + 1));
}
System.out.println("我在for下面");
} catch (InterruptedException e) {
System.out.println("进ErrorInterruptThread2.java类run方法中的catch了!");
e.printStackTrace();
}
}
}
public class ErrorInterruptRu2 {
public static void main(String[] args) {
try {
ErrorInterruptThread2 thread = new ErrorInterruptThread2();
thread.start();
Thread.sleep(2000);
thread.interrupt();
} catch (InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
System.out.println("end!");
}
}

运行结果如下,for下面语句将不再进行运行。



图7 运行结果

在沉睡中停止

1) 如果线程在sleep()状态下停止线程,会是什么效果呢?
代码清单7 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
27
28
29
30
31
public class SleepThread extends Thread {
@Override
public void run() {
super.run();
try {
System.out.println("run begin");
Thread.sleep(200000);
System.out.println("run end");
} catch (InterruptedException e) {
System.out.println("在沉睡中被停止!进入catch!"+this.isInterrupted());
e.printStackTrace();
}
}
}
public class SleepRun {
public static void main(String[] args) {
try {
SleepThread thread = new SleepThread();
thread.start();
Thread.sleep(200);
thread.interrupt();
} catch (InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
System.out.println("end!");
}
}

程序运行后效果如图8所示。如果在sleep状态下停止某一线程,会进入catch语句,并且清除停止状态值,使之变成false。



图8 运行结果

2)前一个实验是先sleep然后再用interrupt()停止,与之相反的操作在学习线程时也要注意。
代码清单7 interrupt状态下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
27
28
29
public class SleepThread2 extends Thread {
@Override
public void run() {
super.run();
try {
for(int i=0;i<100000;i++){
System.out.println("i="+(i+1));
}
System.out.println("run begin");
Thread.sleep(200000);
System.out.println("run end");
} catch (InterruptedException e) {
System.out.println("先停止,再遇到sleep!进入catch!");
e.printStackTrace();
}
}
}
public class SleepRun2 {
public static void main(String[] args) {
SleepThread2 thread = new SleepThread2();
thread.start();
thread.interrupt();
System.out.println("end!");
}
}

控制台输出如下两图所示。



图9 先执行interrupt停止线程


图10 遇到sleep提示异常

能停止的线程–暴力停止

使用stop()方法停止线程则是非常暴力的。
代码清单8 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
public class StopThread extends Thread {
private int i = 0;
@Override
public void run() {
try {
while (true) {
i++;
System.out.println("1=" + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class StopRun {
public static void main(String[] args) {
try {
StopThread thread = new StopThread();
thread.start();
Thread.sleep(8000);
thread.stop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

线程被暴力停止(stop),运行结果如下。



图11 线程被暴力停止

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

调用stop()方法时会抛出java.lang.ThreadDeath异常,但在通常情况下,此异常不需要显式地辅捉。
代码清单9 抛出java.lang.ThreadDeath异常

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

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



图12 进入catch异常

释放锁的不良后果

使用stop()释放锁将会给数据造成不一致的结果。如果出现这样的情况,程序处理的数据集有可能遭到破坏,最终导致程序执行的流程错误,一定要特别注意。
代码清单10 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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class SynchronizedObject {
private String username = "a";
private String password = "aa";
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
synchronized public void printString(String username, String password) {
try {
this.username = username;
Thread.sleep(100000);
this.password = password;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class SynchronizedStopThread extends Thread {
private SynchronizedObject object;
public SynchronizedStopThread(SynchronizedObject object) {
super();
this.object = object;
}
@Override
public void run() {
object.printString("b", "bb");
}
}
public class SynchronizedStopRun {
public static void main(String[] args) {
try {
SynchronizedObject object=new SynchronizedObject();
SynchronizedStopThread thread=new SynchronizedStopThread(object);
thread.start();
Thread.sleep(500);
thread.stop();
System.out.println(object.getUsername()+" "+object.getPassword());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

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



图13 强制stop造成数据不一致

使用return停止线程

将方法interrupt()与return结合使用也能实现停止线程的效果。
代码清单11 return停止线程

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 UseReturnInterruptThread extends Thread {
@Override
public void run() {
while(true){
if(this.isInterrupted()){
System.out.println("停止了!");
return;
}
System.out.println("time="+System.currentTimeMillis());
}
}
}
public class UseReturnInterruptRun {
public static void main(String[] args) throws InterruptedException {
UseReturnInterruptThread t = new UseReturnInterruptThread();
t.start();
Thread.sleep(2000);
t.interrupt();
}
}

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



图14 成功停止运行

Java多线程编程核心技术源代码

Adhere to the original technology to share, your support will encourage me to continue to create!