1 thread.interrupt()方法
该方法实际上只是设置了线程的中断状态为true,并不像stop方法那样会中断一个正在运行的线程,如果设置一个已经处于中断状态的线程,则不会做任何操作,否则设置状态的过程如下:
(1)如果要中断的线程不是当前线程,则会调用要中断的线程的checkAccess()方法,这可能抛出 SecurityException。
(2)如果要中断的线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者在调用该自己的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法,则其中断状态将被清除,它还将收到一个InterruptedException异常。这个时候,我们可以通过捕获InterruptedException异常来终止线程的执行,具体可以通过return等退出或改变共享变量的值使其退出。
(3)如果要中断的线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。这时候处理方法一样,只是捕获的异常不一样而已。
(4)如果该线程在一个 Selector
中受阻,则该线程的中断状态将被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用了选择器的 wakeup
方法一样。(此种情况待研究)
(5)如果以上几步都没有抛出异常,则该线程的中断状态将被设置为true。
interrupt()方法的源码如下:
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();
}
注意:synchronized在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断(请参考后面的测试例子)。与synchronized功能相似的reentrantLock.lock()方法也是一样,它也不可中断的,即如果发生死锁,那么reentrantLock.lock()方法无法终止,如果调用时被阻塞,则它一直阻塞到它获取到锁为止。但是如果调用带超时的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。
2 thread.interrupted()方法
将中断状态复位,即重新设置为false,并返回复位以前的状态。
3 thread.isInterrupted()方法
判断某个线程的中断状态(即判断中断状态是否为true),如果该线程已经中断,则返回true,否则返回false。
在下面还代码所示的例子中,首先创建了两个线程,SleepThread和BusyThread,前者不停
地睡眠,后者一直运行,然后对这两个线程分别进行中断操作,观察二者的中断状态。
public class Interrupted {
public static void main(String[] args) throws Exception {
// sleepThread不停的尝试睡眠
Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
sleepThread.setDaemon(true);
// busyThread不停的运行
Thread busyThread = new Thread(new BusyRunner(), "BusyThread");
busyThread.setDaemon(true);
sleepThread.start();
busyThread.start();
// 休眠5秒,让sleepThread和busyThread充分运行
TimeUnit.SECONDS.sleep(5);
sleepThread.interrupt();
busyThread.interrupt();
System.out.println("SleepThread interrupted is "
+ sleepThread.isInterrupted());
System.out.println("BusyThread interrupted is "
+ busyThread.isInterrupted());
// 防止sleepThread和busyThread立刻退出
SleepUtils.second(2);
}
static class SleepRunner implements Runnable {
@Override
public void run() {
while (true) {
SleepUtils.second(10);
}
}
}
static class BusyRunner implements Runnable {
@Override
public void run() {
while (true) {
}
}
}
}
输出如下:
SleepThread interrupted is false
BusyThread interrupted is true
从结果可以看出,抛出InterruptedException的线程SleepThread,其中断状态没有修改成功,而一直忙碌运作的线程BusyThread,中断状态被修改为false。
4 过期的suspend()、resume()和stop()
suspend()、resume()和stop()方法完成了线程的暂停、恢复和终止工作,但是这些API是过期的,也就是不建议使用的。
不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。
注意:正因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法,而暂停和恢复操作可以用后面提到的等待/通知机制来替代。
5 安全地终止线程
5.1 方式一:使用中断信号量中断非阻塞状态的线程
public class ThreadTest extends Thread {
// 线程中断信号量
volatile boolean stop = false;
public static void main(String[] args) throws Exception {
ThreadTest thread = new ThreadTest();
System.out.println("Starting thread...");
thread.start();
Thread.sleep(3000);
System.out.println("Asking thread to stop...");
// 设置中断信号量
thread.stop = true;
Thread.sleep(3000);
System.out.println("Stopping application...");
}
@Override
public void run() {
// 每隔一秒检测一下中断信号量
while (!stop) {
System.out.println("Thread is running!");
long begin = System.currentTimeMillis();
/**
* 使用while循环模拟sleep方法,这里不要使用sleep,
* 否则在阻塞时会抛InterruptedException异常而退出循环, 这样while检测stop条件就不会执行,失去了意义。
*/
while ((System.currentTimeMillis() - begin < 1000)) {
}
}
System.out.println("Thread exiting under request!");
}
}
5.2 方式二:使用thread.interrupt()中断非阻塞状态线程
public class ThreadTest extends Thread {
public static void main(String[] args) throws Exception {
ThreadTest thread = new ThreadTest();
System.out.println("Starting thread...");
thread.start();
Thread.sleep(3000);
System.out.println("Asking thread to stop...");
// 发出中断请求
thread.interrupt();
Thread.sleep(3000);
System.out.println("Stopping application...");
}
@Override
public void run() {
// 每隔一秒检测一下中断信号量
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Thread is running!");
long begin = System.currentTimeMillis();
/**
* 使用while循环模拟sleep方法,这里不要使用sleep,
* 否则在阻塞时会抛InterruptedException异常而退出循环, 这样while检测stop条件就不会执行,失去了意义。
*/
while ((System.currentTimeMillis() - begin < 1000)) {
}
}
System.out.println("Thread exiting under request!");
}
}
5.3 使用thread.interrupt()+捕捉异常中断阻塞状态线程
public class ThreadTest extends Thread {
public static void main(String[] args) throws Exception {
ThreadTest thread = new ThreadTest();
System.out.println("Starting thread...");
thread.start();
Thread.sleep(3000);
thread.interrupt();// 等中断信号量设置后再调用
System.out.println("Asking thread to stop...");
Thread.sleep(3000);
System.out.println("Stopping application...");
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Thread running...");
try {
/**
* 如果线程阻塞,将不会去检查中断信号量stop变量,所以thread.interrupt()
* 会使阻塞线程从阻塞的地方抛出异常,让阻塞线程从阻塞状态逃离出来,并进行异常块进行相应的处理
*/
Thread.sleep(1000); // 线程阻塞,如果线程收到中断操作信号将抛出异常
} catch (InterruptedException e) {
System.out.println("Thread interrupted...");
/**
* 如果线程在调用Object.wait()方法,或者该类的join()、sleep()方法 过程中受阻,则其中断状态将被清除
*/
System.out.println(this.isInterrupted());// false
// 中不中断由自己决定,如果需要中断线程,则需要重新设置中断位,如果不需要,则不用调用
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
System.out.println("Thread exiting under request...");
}
}
5.4 死锁状态线程无法被中断
public class ThreadTest extends Thread {
public static void main(String args[]) throws Exception {
final Object lock1 = new Object();
final Object lock2 = new Object();
Thread thread1 = new Thread() {
public void run() {
deathLock(lock1, lock2);
}
};
Thread thread2 = new Thread() {
public void run() {
// 注意,这里在交换了一下位置
deathLock(lock2, lock1);
}
};
System.out.println("Starting thread...");
thread1.start();
thread2.start();
Thread.sleep(3000);
System.out.println("Interrupting thread...");
thread1.interrupt();
thread2.interrupt();
Thread.sleep(3000);
System.out.println("Stopping application...");
}
private static void deathLock(Object lock1, Object lock2) {
try {
synchronized (lock1) {
Thread.sleep(10);// 不会在这里死掉
synchronized (lock2) {// 会锁在这里,虽然阻塞了,但不会抛异常
System.out.println(Thread.currentThread());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
System.exit(1);
}
}
}
5.5 中断I/O操作(待研究)
如果线程在I/O操作进行时被阻塞,又会如何?I/O操作可以阻塞线程一段相当长的时间,特别是牵扯到网络应用时。例如,服务器可能需要等待一个请求(request),又或者,一个网络应用程序可能要等待远端主机的响应。
实现此InterruptibleChannel接口的通道是可中断的:如果某个线程在可中断通道上因调用某个阻塞的 I/O 操作(常见的操作一般有这些:serverSocketChannel. accept()、socketChannel.connect、socketChannel.open、socketChannel.read、socketChannel.write、fileChannel.read、fileChannel.write)而进入阻塞状态,而另一个线程又调用了该阻塞线程的 interrupt 方法,这将导致该通道被关闭,并且已阻塞线程接将会收到ClosedByInterruptException,并且设置已阻塞线程的中断状态。另外,如果已设置某个线程的中断状态并且它在通道上调用某个阻塞的 I/O 操作,则该通道将关闭并且该线程立即接收到 ClosedByInterruptException;并仍然设置其中断状态。如果情况是这样,其代码的逻辑和第三个例子中的是一样的,只是异常不同而已。
如果你正使用通道(channels)(这是在Java 1.4中引入的新的I/O API),那么被阻塞的线程将收到一个ClosedByInterruptException异常。但是,你可能正使用Java1.0之前就存在的传统的I/O,而且要求更多的工作。既然这样,Thread.interrupt()将不起作用,因为线程将不会退出被阻塞状态。尽管interrupt()被调用,线程也不会退出被阻塞状态,比如ServerSocket的accept方法根本不抛出异常。
很幸运,Java平台为这种情形提供了一项解决方案,即调用阻塞该线程的套接字的close()方法。在这种情形下,如果线程被I/O操作阻塞,当调用该套接字的close方法时,该线程在调用accept地方法将接收到一个SocketException(SocketException为IOException的子异常)异常,这与使用interrupt()方法引起一个InterruptedException异常被抛出非常相似,(注,如果是流因读写阻塞后,调用流的close方法也会被阻塞,根本不能调用,更不会抛IOExcepiton,此种情况下怎样中断?我想可以转换为通道来操作流可以解决,比如文件通道)。下面是具体实现:
import java.io.IOException;
import java.net.ServerSocket;
public class ThreadTest extends Thread {
volatile ServerSocket socket;
public static void main(String args[]) throws Exception {
ThreadTest thread = new ThreadTest();
System.out.println("Starting thread...");
thread.start();
Thread.sleep(3000);
System.out.println("Asking thread to stop...");
Thread.currentThread().interrupt();// 再调用interrupt方法
thread.socket.close();// 再调用close方法,此句去掉将发生阻塞状态
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println("Stopping application...");
}
public void run() {
try {
socket = new ServerSocket(3036);
} catch (IOException e) {
System.out.println("Could not create the socket...");
return;
}
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Waiting for connection...");
try {
socket.accept();
} catch (IOException e) {
System.out.println("accept() failed or interrupted...");
Thread.currentThread().interrupt();// 重新设置中断标示位
}
}
// 判断线程是否被阻塞,如果被阻塞则无法打印此句
System.out.println("Thread exiting under request...");
}
}
6 中断异常处理方式
不要在你的底层代码里捕获InterruptedException异常后不处理,或处理不当,如下:
void mySubTask(){
...
try{
sleep(delay);
}catch(InterruptedException e){}//不要这样做
...
}
如果你不知道抛InterruptedException异常后如何处理,那么你有如下好的建议处理方式:
1、在catch子句中,调用Thread.currentThread.interrupt()来设置中断状态(因为抛出异常后中断标示会被清除),让外界通过判断Thread.currentThread().isInterrupted()标示来决定是否终止线程还是继续下去,应该这样做:
void mySubTask() {
...
try {
sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupted();
}
...
}
2、或者,更好的做法就是,不使用try来捕获这样的异常,让方法直接抛出
void mySubTask() throws InterruptedException {
...
sleep(delay);
...
}