本文转载https://my.oschina.net/ljhlgj/blog/1572376
中断线程
thread.interrupt()用来中断线程,即将线程的中断状态位设置为true,注意中断操作并不会终止线程,不像stop()会立即终止一个运行中的线程,中断仅仅是将线程中断位设置为true(默认false)。线程会不断的检查中断位,如果线程处于阻塞状态(sleep、join、wait)且中断,就会抛出InterreptException来唤醒线程,交由应用程序处理;如果线程未阻塞且中断,也要交由应用程序处理;是终止线程,还是继续执行需要根据实际情况做出合理的响应。
如何响应线程中断
thread.interrupt()后,线程的中断位设置为true,没有任何语言方面的需求一个被中断的线程应该终止。中断只是为了引起该线程的注意,来决定如何应对中断。有些很重要的线程,以至于他们不理会中断,而是继续执行,但更多的情况下,线程应该把中断看做是一个终止请求,在终止线程前,有时间做一些收尾清理工作,如下:
public void run() {
try {
/*
* 不论while中是否调用过线程阻塞的方法,如sleep、join、wait,这里还是需要加上
* !Thread.currentThread().isInterrupted()条件来判断线程是否终止。
* 原因:即使调用了阻塞方法但线程可能仍没有阻塞,这样会更安全、更及时。
*/
while (!Thread.currentThread().isInterrupted()) {
//do more work
}
} catch (InterruptedException e) {
//线程在阻塞期间被中断了
} finally {
//线程结束前做一些清理工作
}
}
注意:这里使用Thread.currentThread().isInterrupted(),而不是**Thread.isInterrupted()**来判断线程是否中断,因为Thread.isInterrupted()判断线程中断位为true后,会将中断位设置为false,而Thread.currentThread().isInterrupted()不会将中端位在设置为false。线程阻塞中断是抛出异常后,同样会将中断位设置为false。
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
...
sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();//重新设置中断位,否则线程不能退出
}
}
}
注意:synchornized、reentrantLock.lock()获取锁操作造成的阻塞在中断状态下不能抛出InterruptException,即获取锁操作是不能被中断的,要一直阻塞等到到它获取到锁为止。也就是说如果产生了死锁,则不能被中断。可以使用超时锁来打破死锁,reentrantLock.tryLock(timeout,unit)在timeout后会抛出InterruptException,唤醒线程做响应操作
在捕获到InterruptException后如何处理呢,通常有如下两种方式,这两种方式本质上是一样的,都是将中断交由用户应用程序来处理。
- 1.在catch子句中调用Thread.currentThread.interrupt()来重设中断状态位(因为抛出中断异常后,中断状态位会被清除,即false),让外界判断Thread.currentThread.isInterrupted()来决定线程的行为。
try {
sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().isInterrupted();
}
-
- 更加直接,不用捕获异常,直接抛出InterruptedException,让用户程序去处理异常。
try {
sleep(delay);
} catch (InterruptedException e) {
throw e;
}
终止线程
让一个运行中的线程终止,通常有两种方法:1.使用信号量;2.使用线程中断。
- 1.使用信号量终止线程:设置一个变量(信号量),通过更改变量的状态,来决定线程是否要终止,该方式不能终止阻塞的线程。
class Scheduler{
volatile boolean stop = false;// 线程中断信号量
private Worker worker = new Worker("my task worker");
void start(){
worker.start();
stop=false;
}
void stop(){
stop=true;
}
class Worker extend Thread{
public void run() {
while (!stop) {
long time = System.currentTimeMillis();
/*
* 用while循环模拟sleep(),这里不要用sleep,否则在线程阻塞时可能会抛
* InterruptedException而退出,这样while检测stop条件就不会执行,失去了意义。
*
* 不论是模拟sleep,还是使用sleep,都建议线程任务中做这样的操作,以控制线程执行速率
*/
while ((System.currentTimeMillis() - time < 1000)) {
//do something
}
}
}
}
}
注意:信号量一定要是多线程可见的,这里使用volatile关键字,也可以使用Automicxxx
- 2.使用线程中断来终止线程:用thread.interrupt()设置线程中断位为true,让应用来决定线程是否要终止,该方式能让阻塞的线程抛出InterruptException来唤醒线程,从而决定线程的行为。
class Scheduler{
private Worker worker = new Worker("my task worker");
void start(){
worker.start();
}
void stop(){
worker.interrupt();//设置线程中断位为true
}
class Worker extend Thread{
public void run() {
while (!Thread.currentThread.isInterrupted()) {
try{
// do something
Thread.sleep(10);
}catch(InterruptException e){
// 重新设置线程状态位
Thread.currentThread.interrupted();
}
}
}
}
}
注意:这里也可以使用信号量代替Thread.currentThread.isInterrupted(),在捕获到异常后,设置信号量即可,但不太简便。
I/O阻塞中断
i/o操作可以阻塞线程很长时间,特别是牵扯到网络socket时,那么在i/o阻塞时,线程中断会有什么现象呢?有两种情况:
- 1.线程在可中断通道(实现InterruptibleChannel接口)上,调用io阻塞操作(serverSocketChannel.accept(),socketChannel.connect(),socketChannel.open(),socketChannel.read(),socketChannel.write()等)而阻塞线程后,又被设置为中断时(调用thread.interrupt()),通道将被关闭,并在io阻塞处会抛出ClosedByInterruptException,这样应用程序就能响应改中断异常,做出合适的处理。
- 2.线程在不可中断通道(java1.0前的传统io模型)上,调用io阻塞操作,即使线程被设置为中断,也不会唤醒阻塞线程,因为这样的io阻塞在线程中断后不会抛出任何异常。幸运的是java平台提供了一种解决方案:调用阻塞改线程的socket的close方法,此时在socket.accept()处就抛出SocketException,这与thread.interrupt() 引起InterruptException抛出很相似,可以响应SocketException来终止并退出线程。
总结
- 1.中断一个线程,只是为了引起该线程的注意(唤醒阻塞),让其自己决定如何应对,而不是像stop一样直接终止掉线程。
- 2.阻塞的线程如果被中断,会在阻塞处抛出InterruotException来唤醒线程,并将线程中断标志位设置为false,因为线程已经处理了异常(抛出),就要回到就绪状态。
- 3.synchronized,Lock.lock获取锁操作不能被中断,如果拿不到资源,会一直阻塞下去,可以使用可中断的加锁操作Lock.lockInterruptibly(),也可以使用超时锁Lock.tryLock(timeout,unit),在超时后会抛出异常。
- 4.中断其实是线程的一种协作机制,比直接结束线程操作弱一点,给程序一些时间来决定如何处理,如sleep方法对中断的处理是抛出中断异常,并重置中断位置。