线程的中断
stop()方法的线程不安全性
Thread.stop()方法可以停止一个运行中的线程,但是这个方法已经被弃用了,这是为什么呢?
先看下面一段代码:
package com.morris.concurrent.thread.interrupt;
import java.util.concurrent.TimeUnit;
/**
* 演示stop()方法带来的线程安全问题
*/
public class StopDemo {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(() -> {
try {
synchronized (lock) {
System.out.println("thread->" + Thread.currentThread().getName() + " acquire lock.");
TimeUnit.SECONDS.sleep(3);
System.out.println("thread->" + Thread.currentThread().getName() + " 等待3s");
System.out.println("thread->" + Thread.currentThread().getName() + " release lock.");
}
} catch (Throwable e) {
// 抛出ThreadDeath Error
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("thread->" + Thread.currentThread().getName() + " acquire lock.");
}
});
t1.start();
// 休眠100ms,等待t1线程运行
TimeUnit.MILLISECONDS.sleep(100);
t2.start();
t1.stop();
}
}
运行结果如下:
thread->Thread-0 acquire lock.
thread->Thread-1 acquire lock.
java.lang.ThreadDeath
at java.lang.Thread.stop(Thread.java:853)
at com.morris.concurrent.thread.interrupt.StopDemo.main(StopDemo.java:41)
从运行结果中我们可以看到,调用了t1.stop()方法后,可以发现,t1线程抛出了ThreadDeath Error并且t1线程释放了它所占有的锁。
一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。
这个也是不提倡使用kill -9
命令强制杀死线程的原因,这样可能会导致一些难以处理的线程安全问题。
interrupt()中断线程
stop()方法除了会产生线程安全问题外,而且对被终止线程是一种强制式的终止。而在java中,线程之间更多的是提倡一种协作式。
每一个线程都有一个boolean类型标志,用来表明当前线程是否被中断。interrupt()方法是用来设置线程的中断标志位为true,被中断的线程可以自行决定是否对这个中断进行处理(直接终止或者进行数据保护等处理后在终止或者不终止)。
我们可以通过调用Thread.currentThread().isInterrupted()或者Thread.interrupted()来检测线程的中断标志是否被置位。
这两个方法的区别是:
- Thread.currentThread().isInterrupted():对象方法,不清除中断标志位。
- Thread.interrupted():静态方法,清除中断标志位。
所以说调用线程的interrupt()方法不会中断一个正在运行的线程,这个机制只是设置了一个线程中断标志位,如果在程序中你不检测线程中断标志位,那么即使设置了中断标志位为true,线程也一样照常运行。
一般来说中断线程分为三种情况:
- 中断非阻塞线程
- 中断阻塞线程
- 中断不可中断线程
中断非阻塞线程
采用线程共享变量
这种方式比较简单可行,需要注意的一点是共享变量必须设置为volatile,这样才能保证修改后其他线程立即可见。
package com.morris.concurrent.thread.interrupt;
/**
* 中断非阻塞的线程:使用共享变量
*/
public class InterruptNonBlockWithShareVariable extends Thread {
// 设置线程共享变量
private volatile boolean isStop = false;
@Override
public void run() {
while(!isStop) {
long beginTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " is running");
// 当前线程每隔一秒钟检测一次线程共享变量是否得到通知
while (System.currentTimeMillis() - beginTime < 1000) {
}
}
if (isStop) {
System.out.println(Thread.currentThread().getName() + " is interrupted");
}
}
public static void main(String[] args) throws InterruptedException {
InterruptNonBlockWithShareVariable thread = new InterruptNonBlockWithShareVariable();
thread.start();
Thread.sleep(5000);
thread.isStop = true;
}
}
采用中断机制
package com.morris.concurrent.thread.interrupt;
/**
* 中断非阻塞的线程:使用中断机制
*/
public class InterruptNonBlockWithInterrupt extends Thread {
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()) {
long beginTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " is running");
// 当前线程每隔一秒钟检测一次线程共享变量是否得到通知
while (System.currentTimeMillis() - beginTime < 1000) {
}
}
System.out.println(Thread.currentThread().getName() + " interrupted flag:" + Thread.currentThread().isInterrupted());
System.out.println(Thread.currentThread().getName() + " is interrupted");
}
public static void main(String[] args) throws InterruptedException {
InterruptNonBlockWithInterrupt thread = new InterruptNonBlockWithInterrupt();
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
中断阻塞线程
当线程调用Thread.sleep()、Thread.join()、Object.wait()、LockSupport.park()或者调用阻塞的i/o操作方法时,都会使得当前线程进入阻塞状态。
如果在线程处于阻塞状态时,调用interrupt()方法设置线程中断标志位时会抛出一个InterruptedException异常,并且会清除线程中断标志位(设置为false),这样一来线程就能退出阻塞状态。
如果线程已经被中断了,然后再调用阻塞方法,那么会直接抛出异常。
采用共享变量
package com.morris.concurrent.thread.interrupt;
/**
* 中断阻塞的线程:使用共享变量
* 缺点:使用共享变量无法及时中断阻塞线程,因为线程不能第一时间接到中断通知,需等线程被唤醒时才知道。
*/
public class InterruptBlockThreadWithVariable extends Thread {
// 设置线程共享变量
private volatile boolean isStop = false;
@Override
public void run() {
while(!isStop) {
System.out.println(Thread.currentThread().getName() + " is running");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + " is interrupted");
}
public static void main(String[] args) throws InterruptedException {
InterruptBlockThreadWithVariable thread = new InterruptBlockThreadWithVariable();
thread.start();
Thread.sleep(1000);
thread.isStop = true;
}
}
缺点:使用共享变量无法及时中断阻塞线程,因为线程不能第一时间接到中断通知,需等线程被唤醒时才知道。
采用中断机制
package com.morris.concurrent.thread.interrupt;
/**
* 中断阻塞的线程:使用中断机制
* 注意抛出异常会清除中断标志位
*/
public class InterruptBlockThreadWithInterrupt extends Thread {
@Override
public void run() {
// 这里调用的是非清除中断标志位的isInterrupted方法
while(!Thread.currentThread().interrupted()) {
System.out.println(Thread.currentThread().getName() + " is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 由于调用sleep()方法清除状态标志位 所以这里需要再次重置中断标志位 否则线程会继续运行下去
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + " is interrupted");
}
public static void main(String[] args) throws InterruptedException {
InterruptBlockThreadWithInterrupt thread = new InterruptBlockThreadWithInterrupt();
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
暴力停止不可中断线程
package com.morris.concurrent.thread.interrupt;
import java.util.concurrent.TimeUnit;
/**
* 暴力停止不可中断的线程,如正在获取synchronized锁的线程
*/
public class ViolenceStopNotInterruptThread {
public static void main(String[] args) throws InterruptedException {
ThreadService threadService = new ThreadService();
threadService.execute(() -> {
while (true) {
System.out.println("I am working...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 等待threadService里面的线程充分运行
TimeUnit.MILLISECONDS.sleep(1000);
threadService.shutdown(1_000);
}
private static class ThreadService {
// 这个线程负责启动一个守护线程,当这个线程结束时,它创建的线程也会随之结束
private Thread executeThread;
// 用于标记任务是否结束
private boolean isStop = false;
/**
* 执行任务
* @param task
*/
public void execute(Runnable task) {
executeThread = new Thread(() -> {
// t是真正执行任务的线程
Thread t = new Thread(task);
t.setDaemon(true);
t.start();
try {
// 这里要join,不然守护线程还没开始运行就可能结束了,另外也便于超时结束
executeThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
isStop = true;
});
executeThread.start();
}
/**
* 等待mills还未结束就终止线程
* @param mills
*/
public void shutdown(long mills) {
long endMills = System.currentTimeMillis() + mills;
long remainMills = mills;
while (remainMills > 0 && !isStop) {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
remainMills = endMills - System.currentTimeMillis();
}
// 中断线程,executeThread结束后,其创建的守护线程也会随之结束
executeThread.interrupt();
}
}
}