问题背景
今天查看项目的代码的时候,发现同事写了一段代码,想通过Thread.interrupt来退出正在执行的线程,代码大概如下:
public class ThreadInterruptTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
try {
// do something
// do something
// do something
Thread.sleep(3 * 1000); //A处
/* do something
do something
do something */
} catch (InterruptedException e) { //C处
Thread.currentThread().interrupt //D处
break;
}
}
});
//运行线程
t1.start();
Thread.sleep(10 * 1000);
//打断线程
t1.interrupt(); //B处
}
}
这段代码希望通过B处执行的t1.interrupt来打断线程t1的执行。
仔细看会发现这段代码是有问题的。我们先来看下Thread.interrupt方法的源码以及它的注释(每一段英文注释我都简单的翻译了下,如有不正确的地方,劳烦各位指出):
/**
* Interrupts this thread.
* 打断当前这个线程
*
* <p> Unless the current thread is interrupting itself, which is
* always permitted, the {@link #checkAccess() checkAccess} method
* of this thread is invoked, which may cause a {@link
* SecurityException} to be thrown.
* 无论什么时候当前线程内interrupt自己都是被允许的。但是如果当前线程interrupt的
* 不是当前线程,则会调用checkAccess方法,该方法内通过SecurityManager来检查这个
* 操作是否能被允许。
*
* <p> If this thread is blocked in an invocation of the {@link
* Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
* Object#wait(long, int) wait(long, int)} methods of the {@link Object}
* class, or of the {@link #join()}, {@link #join(long)}, {@link
* #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
* methods of this class, then its interrupt status will be cleared and it
* will receive an {@link InterruptedException}.
* 如果被打断的线程正因为以下情况而被阻塞:Object.wait,Thread.join,Thread.sleep,
* 调用interrupt方法后会将被打断的线程的interrupt标志位重置,并抛出InterruptedException
*
* <p> If this thread is blocked in an I/O operation upon an {@link
* java.nio.channels.InterruptibleChannel InterruptibleChannel}
* then the channel will be closed, the thread's interrupt
* status will be set, and the thread will receive a {@link
* java.nio.channels.ClosedByInterruptException}.
* 如果被打断的线程正被阻塞在一个InterruptibleChannel对象的I/O操作上,调用
* interrupt方法同样也会将被打断的线程的interrupt标志位重置,并抛出ClosedByInterruptException
*
* <p> If this thread is blocked in a {@link java.nio.channels.Selector}
* then the thread's interrupt status will be set and it will return
* immediately from the selection operation, possibly with a non-zero
* value, just as if the selector's {@link
* java.nio.channels.Selector#wakeup wakeup} method were invoked.
* 如果被打断的线程正被Selector阻塞,则调用interrupt后,当前线程的interrupt标志位会被设置,
* 且将会马上从这个selection操作上返回,返回值大概率为一个非0的值。上述调用interrupt方法的效果,和
* 调用Selector的wakeup方法是等效的。
*
* <p> If none of the previous conditions hold then this thread's interrupt
* status will be set. </p>
* 如果在调用当前线程的interrupt方法的时候,不属于上述任意一种情况,则interrupt操作
* 只会去设置当前线程的interrupt标志位,将其设置为true。
*
* <p> Interrupting a thread that is not alive need not have any effect.
* interrupt一个非alive的线程将不会有任何的效果。
*
* @throws SecurityException
* if the current thread cannot modify this thread
*
* @revised 6.0
* @spec JSR-51
*/
public void interrupt() {
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();
}
结合上述Thread.interrupt方法的注释以及我同事写的代码。一共有下述两个错误点:
1、 当执行B处的代码时,线程t1的while循环中并不一定正在执行Thread.sleep,所以不一定会抛出InterruptedException。
2、 如果执行B处的代码时,线程t1恰好在sleep(执行A处的代码),会抛出InterruptedException,为了能退出当前线程,其实只要写个break就行了,没必要加上D处的代码,再次interrupt一下当前的线程(应该是没理解interrupt方法的作用,以为调用interrupt方法就会打断当前正在运行的线程并将它退出)。
改进同事的代码
public class ThreadInterruptTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (!Thread.interrupted()) {
// do something
// do something
// do something
// Thread.sleep(3 * 1000);
// do something
// do something
// do something
}
});
//运行线程
t1.start();
Thread.sleep(10 * 1000);
//打断线程
t1.interrupt();
}
}
Thread.interrupt小结
Thread.interrupt方法的逻辑是不会打断线程的,它只是会将当前线程的interrupt标志位设置为true。如果你的代码逻辑中要要响应Thread.interrupt方法,则只需要判断当前线程的interrupt标志位的值就可以了,可以通过Thread的isInterrupted和interrupted方法(两个方法的区别是前者调用后不会重置线程的interrupt标志位,而后者会)来获取当前线程的interrupt状态,根据interrupt的状态来执行自己期望的逻辑。没有看过Thread.sleep(native方法)的源码,但是我猜应该也是按照上述方式来实现对Thread.interrupt方法的响应。