Thread.sleep()为什么要抛出中断异常或者放入try-catch中?
因为:在 sleep 的同时也要对外界情况有感知能力,也就是能够响应中断。比如在调用 interrupt() 的时候,其实就是想尽快地结束线程,所以,继续的 sleep 是没有意义的,应该尽快结束。
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
try {
sleep(10000);
} catch (InterruptedException e) {
System.out.println("sleep 状态被中断了!");
e.printStackTrace();
}
}
};
thread.start();
// 睡眠两秒后标记中断线程
try {
sleep(2000);
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
如何正确终止线程?
最直接了断的方法就是调用 stop(),它能直接结束线程的执行,但是已经弃用。
这个方法原本是设计用来停止线程并抛出线程死亡的异常,但是实际上它是不安全的,因为它是直接终止线程解放锁,这很难正确地抛出线程死亡的异常进行处理
所以,更好的方式是设置一个判断条件,然后在线程执行的过程中去判断该条件以去决定是否要进行停止线程,这样就能进行处理并有序地退出这个线程。假如一个线程等待很久的话,那才会直接中断线程并抛出异常。
按照上面所说,我们可以设置一个变量来进行控制,当然,我们可以声明一个 bool 类型进行判断,但是更好的方式是使用 interrupt()。
Thread thread = new Thread(){
@Override
public void run() {
while (!isInterrupted()){
System.out.println("Thread is running");
}
}
};
thread.start();
// 睡眠两秒后标记中断线程
try {
sleep(2000);
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
上述代码中值得注意的是,在thread.interrupt()
方法中,只是将线程的 private volatile boolean interrupted
设置为true,所以还需要在原线程中通过 isInterrupted()
方法来检查
interrupt() 和 isInterrupted() 源码:
public void interrupt() {
if (this != Thread.currentThread()) {
checkAccess();
// thread may be blocked in an I/O operation
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupted = true;
interrupt0(); // inform VM of interrupt
b.interrupt(this);
return;
}
}
}
interrupted = true;
// inform VM of interrupt
interrupt0();
}
public boolean isInterrupted() {
return interrupted;
}
除了上述方法,还可以通过在while中判断 volatile 的boolean变量判断是否终止:
static class Runner implements Runnable{
private volatile boolean on = true;
private long i = 0;
@Override
public void run() {
while (on && !Thread.currentThread().isInterrupted()){
i++;
}
System.out.println("Count i = " + i);
}
public void cancel(){
on = false;
}
}
中断操作 或 标识位 的方式能使线程在终止时有机会去清理资源,而不是武断地将线程停止,更为安全和优雅
中断清除
一般代码都会这样写:(目的:休眠100毫秒后,判断线程是否被中断,如果未被中断则继续执行业务
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//中断标志已经被清除了
}
// Thread.currentThread().isInterrupted():是否被中断了(是否有中断标志)
if(!Thread.currentThread().isInterrupted()) {
//如果没有被中断,则处理业务
doSomething();
}
但是即使线程在sleep期间被中断,我们下面的代码依然会执行。为什么呢?就是因为sleep是会擦除中断标志的,抛异常的同时,该线程的中断状态会被清除:
如果要实现休眠100毫秒后,判断线程是否被中断,如果未被中断则继续执行业务该怎么办?很简单,我们在本线程再手动中断一次即可 ,这样业务代码就不会执行了:
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//中断标志已经被清除了
// 手动中断本线程,将本线程打上中断信号。
Thread.currentThread().interrupt();
}
// Thread.currentThread().isInterrupted():是否被中断了(是否有中断标志)
if(!Thread.currentThread().isInterrupted()) {
//如果没有被中断,则处理业务
doSomething();
}
interrupted() 和 isinterrupted()
interrupted() 是一个静态方法,它会检查当前线程的中断状态并清除中断状态:如果线程被中断,即中断状态为true,则 interrupted() 方法会返回true,并且会将中断状态重置为false。如果线程没有被中断,即中断状态为false,则 interrupted() 方法会返回false。
而 isInterrupted() 是一个实例方法
代码测试验证对比
实战
List shutdownNow():对所有正在执行的任务线程发送中断信号。等待队列的任务会被返回
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(()->{
//任务一直跑
while (true) {
System.out.println("1");
}
});
Thread.sleep(3000);
executorService.shutdownNow();
System.out.println("shutdown");
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(()->{
//任务一直跑
while (!Thread.currentThread().isInterrupted()) {
System.out.println("1");
}
});
Thread.sleep(3000);
executorService.shutdownNow();
System.out.println("shutdown");
shutdownNow 方法的解释:There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. This implementation interrupts tasks via Thread.interrupt; any task that fails to respond to interrupts may never terminate
该方法中最终是调用了每个线程 t 的 interrupt() 方法,所以第一种 while true 会一直输出 1 无法停止。而第二种方法则在3s后停止,最后打印 shutdown