线程中断和终止

线程中断和终止

第一节 简述

  Java中的中断机制是一种协作机制,即中断并不会立即终止线程。中断可以理解为线程的一个标识属性(flag),当其他线程调用此线程的interrupt()时,对此线程进行中断操作。

  当run()执行完方法体最后一条语句经return返回时,或因未捕获的异常跳出时,线程会被终止。interrupt()可以用来请求终止线程,当线程调用interrupt(),其中断状态将被置位。线程应该不时的检查此中断标志,判断自己是否被中断。还有一个stop()方法可以强制终止线程,已被弃用。

结束线程的方式

  • 标志位:表示线程正常结束,run()执行完毕。
  • **interrupt()**:
  • **stop()**:已废弃

第二节 suspend()、resume()和stop()

  此三个方法分别实现了线程的暂停、恢复和停止操作。但因为其副作用而被停用,可以采用其他替代方案来代替暂停和恢复。被停用的原因:休眠线程后并不会释放其占用的资源,所以会导致死锁。


第三节 通过interrupt()和isInterrupted()终止线程

  线程则通过检查自己是否被中断进行响应,通过isInterrupted()来判断,也可以通过静态方法Thread.interrupted()对当前线程的中断标识位进行判断并复位。若该线程已处于终结状态,即使其已被中断过,在调用此线程对象的isInterrupted()时依旧会返回false。

  日常使用可以如下对线程中断状态进行判断。

1
2
3
while(!Thread.currentThread().isInterrupted()){

}

  Thread中与中断相关的源码如下。

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
// 测试当前线程是否被中断,根据ClearInterrupted=true将中断状态复位,只要执行就会将当前状态重置为false
public static boolean interrupted(){
return currentThread().isInterrupted(true);
}

@HotSpotIntrinsicCandidate
public static native Thread currentThread();//静态方法currentThread()获取当前线程

public boolean isInterrupted() {
return isInterrupted(false);
}

@HotSpotIntrinsicCandidate
private native boolean isInterrupted(boolean ClearInterrupted);//测试是否有线程被中断,中断状态是否基于传递的参数值重置。
private native void interrupt0();

public void interrupt() {//向线程发送中断请求,线程的中断状态被设置为true,若线程阻塞,则抛出异常
if (this != Thread.currentThread())
checkAccess();

synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}

@Deprecated(since="1.2", forRemoval=true)
public final synchronized void stop(Throwable obj) {
throw new UnsupportedOperationException();
}

3.1 阻塞方法

  当线程被阻塞时,无法检测中断状态,也就是产生异常InterruptedException的地方。所以当一个方法抛出InterruptedException时表示当前方法是一个阻塞方法

  普通方法的完成可能只取决于要做什么、有多少计算资源可用。而阻塞方法的完成还要取决于一些外部事件(如计时器到期、I/O完成、其他线程释放锁、其他线程设置一个标志、其他线程将一个任务放到工作队列中等)。

  因此阻塞方法能够被取消掉是一个比较重要的属性(当然耗时的非阻塞方法也应该可被取消)。Thread所提供的中断机制,并通过Thread.sleep()和Thread.wait()支持

  当在一个被阻塞的线程(调用过sleep()或wait())上调用interrupt()阻塞调用将会被InterruptedException异常中断。对于一些抛出InterruptedException的方法,在抛出异常前JVM就会擦除中断标识位,然后再抛出异常,所以isInterrupted()仍会返回false

  中断并不意味着线程终止,被中断的线程可以决定如何响应中断请求,也可以不理会中断,普遍的情况是把中断请求当作终止请求,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
Runnable r1 = () -> {
try{
while(!Thread.currentThread().isInterrupted()){
//继续工作
Thread.sleep(delay);
}
}catch (InterruptedException ex){//建议直接在底层方法抛出InterruptedException异常,让调用者去捕获此异常
//进程阻塞时被请求中断
}finally {
//释放资源,清理数据
}
}

  在中断状态被置位时调用sleep()方法并不会使线程休眠,反而会清除此状态并抛出InterruptedException,若在如上循环中加入sleep(),则没有必要用isInterrupted(),因为不会检测中断状态。


第四节 通过标志位方式终止线程

  中断操作是线程间的一种简单的交互方式,适合用来取消或停止任务。也可以通过标志位的方式来终止线程,通常会使用volatile声明一个布尔变量标识是否已完成任务。

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
public class Shutdown {
private static class Runner implements Runnable{
private long i;
private volatile boolean on = true;

@Override
public void run(){
while (on && !Thread.currentThread().isInterrupted()){
i++;
}
System.out.println("Count i = " + i);
}

public void cancel(){
on = false;
}
}

public static void main(String[] args)throws Exception{
//声明并启动线程线程1
Runner one = new Runner();
Thread countThread = new Thread(one,"CountThread");
countThread.start();
//睡眠1秒,main线程对CountThread进行中断,使CountThread能够感知中断而结束
TimeUnit.SECONDS.sleep(1);
countThread.interrupt();

//声明并启动线程线程2
Runner two = new Runner();
countThread = new Thread(two,"CountThread");
countThread.start();
//睡眠1秒,main线程对Runner two进行取消,使CountThread能够感知on为false而结束
TimeUnit.SECONDS.sleep(1);
two.cancel();
}
}

  main线程通过中断操作和cancel()方法均可以使线程CountThread得以终止。这类通过标识位或中断操作的方法能够使线程在终止时可以有机会去清理资源,而不是直接将线程停止,因此这类方法更加安全和优雅。


参考博客和文章书籍等:

《Java并发编程的艺术》

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容