本文主要介绍Java线程中断一些相关的概念以及注意点
Java线程的中断并不是强制的中断,调用线程中断的方法时只是起到一个通知的作用,至于线程是否要继续执行下去取决于线程自身的处理。
除去已经不推荐使用的thread.stop()方法,主要讲一下线程的成员方法thread.interrupt()、thread.isInterrupted()以及静态方法Thread.interrupted()
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.
*
* <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}.
*
* <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}.
*
* <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.
*
* <p> If none of the previous conditions hold then this thread's interrupt
* status will be set. </p>
*
* <p> Interrupting a thread that is not alive need not have any effect.
*
* @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();
}
复制代码
上面是官方关于interrupt()定义,大致的意思是:
线程中断自己是被允许的。除非当前线程正在中断,否则当前线程的checkAccess()会被调用,这可能会抛出SecurityException异常
当线程被Object中定义的 wait()、wait(long)或wait(long, int)以及线程中的jion()、join(long)、join(long, int)、sleep(long)或sleep(long, int)进入阻塞状态,调用interrupt()时线程的中断标记会被清除,同时抛出一个InterruptedException异常。
如果当前线程在可中断通道的I / O操作中被阻塞,则通道将被关闭,线程的中断状态将被设置为true,并且线程将收到ClosedByInterruptException。
如果当前线程在选择器中被阻塞,那么线程的中断状态将被设置为true,并且它将立即从选择操作中返回,可能具有非零值,就像调用选择器的唤醒方法一样。
如果上述情况都没有发生,那么线程的中断状态将被设置为true。
isInterrupted()、interrupted()
/**
* Tests whether the current thread has been interrupted. The
* <i>interrupted status</i> of the thread is cleared by this method. In
* other words, if this method were to be called twice in succession, the
* second call would return false (unless the current thread were
* interrupted again, after the first call had cleared its interrupted
* status and before the second call had examined it).
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if the current thread has been interrupted;
* <code>false</code> otherwise.
* @see #isInterrupted()
* @revised 6.0
*/
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
复制代码
上面注释主要是这个意思:
这个方法主要用于测试当前线程是否被中断,同时当前线程的中断状态会被清除。换句话说,连续调用两次这个方法,返回的结果肯定是false,当然有例外,就是第一次调用完这个方法且第二次尚未开始调用的时候,线程再次被中断,也就是中断状态再次被设置为true的情况。
当线程不存活时,线程的中断将被忽略,通过这个方法返回false来反映。
注意点是,这是Thread的静态方法
/**
* Tests whether this thread has been interrupted. The <i>interrupted
* status</i> of the thread is unaffected by this method.
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if this thread has been interrupted;
* <code>false</code> otherwise.
* @see #interrupted()
* @revised 6.0
*/
public boolean isInterrupted() {
return isInterrupted(false);
}
复制代码
这个方法和上面的静态方法的主要区别是,这个成员方法调用后并不清除中断状态
线程中断
从上面的3个方法的注释可以看出,线程的中断并不是强制中断,除了可以能抛出异常的情况,都需要自己去处理。对于抛出异常情况,如果自己不想处理,最好是将异常往上传递,可以给其他用户一个处理异常的机会。如果对interrupt不处理又不反馈任何信息,就会像下面这样。
典型的synchronized对线程的中断并不处理
public class ThreadInterruptTest {
public static void main(String[] args) {
try {
Object object = new Object();
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (object) {
Thread.sleep(5000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(2000);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object){
System.out.println("get lock");
}
}
});
thread.start();
Thread.sleep(1000);
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
复制代码
5秒之后打印:
get lock
复制代码
因为synchronized不处理,线程阻塞在获取锁的状态根本中断不了。
对于想中断的线程,我们可以这样操作:
public class ThreadInterruptTest {
public static void main(String[] args) {
try {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
Thread.sleep(1000);
System.out.println("in while loop");
}
} catch (InterruptedException e) {
System.out.println("InterruptedException");
}
}
});
thread.start();
Thread.sleep(4000);
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
复制代码
打印如下:
in while loop
in while loop
in while loop
InterruptedException
复制代码
正常中断了线程
再来看一种特殊情况:
public class ThreadInterruptTest {
public static void main(String[] args) {
try {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
System.out.println("in while loop");
} catch (InterruptedException e) {
System.out.println("InterruptedException");
}
}
}
});
thread.start();
Thread.sleep(4000);
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
复制代码
打印是这样的:
in while loop
in while loop
in while loop
InterruptedException
in while loop
in while loop
in while loop
in while loop
...
复制代码
程序进入了死循环,猜到什么原因了吗?上面关于thread.interrupt()的注释里面明确说明了,当抛出InterruptedException异常时,线程的中断标记会被清除!!!这就是引起死循环的原因。我们可以这样处理,在捕捉到异常后后加上Thread.currentThread().isInterrupted();再次设置中断的标记。或者像最开始的写法,将整个while循环放try里面。
当然也可以通过自己维护flag的形式来中断线程
public class ThreadInterruptTest {
private static volatile boolean interruptFlag = false;
public static void main(String[] args) {
try {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (!interruptFlag) {
System.out.println("in while loop");
}
System.out.println("interrupted");
}
}) {
@Override
public void interrupt() {
super.interrupt();
interruptFlag = true;
}
};
thread.start();
Thread.sleep(2000);
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
复制代码
最后看一下调用interrupted(),清除中断标记的情况
public class ThreadInterruptTest {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.interrupted());
System.out.println(Thread.interrupted());
}
});
thread.start();
thread.interrupt();
}
}
复制代码
打印如下:
true
false
复制代码
总结
- 线程调用interrupt()之后并一定会中断,例如在等待synchronized锁的状态下
- 线程在sleep()、wait()、jion()等阻塞状态下被中断会抛出InterruptedException异常,同时中断标记被清除
- 连续调用两次静态方法interrupted()第二次将返回false(特殊情况注意一下)