java thread interrupt mechanism

如何中断线程

通过Thread#interrupt()方法中断线程,这里的中断并不是stop线程,而只是设置线程的中断状态位,是否中断是取决于线程本身。

实际上Java的线程中断是一种线程之间的协作机制,也就是说调用线程对象的Thread#interrupt()方法并不一定就中断了正在运行的线程,它只是要求线程自己在合适的时机中断自己,每个线程都有一个boolean的中断状态(这个状态不在Thread的属性上),Thread#interrupt()方法仅仅只是将该状态置为true

判断线程是否被中断

使用Thread#isInterrupted()方法,一般是这样使用:while(!Thread.currentThread().isInterrupted()) {}

线程中断相关方法说明

Method Description
public static boolean interrupted() 测试当前线程是否已经中断,线程的中断状态由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回false
public boolean isInterrupted() 测试线程是否已经中断。线程的中断状态不受该方法的影响。
public void interrupt() 中断线程。

线程中断和线程阻塞

java中有很多方法会导致线程阻塞:

  • Object.wait()
  • Thread.sleep()
  • Process.waitFor()
  • AsynchronousChannelGroup.awaitTermination()
  • ExecutorService.awaitTermination()
  • Future.get()
  • BlockingQueue.take()
  • Semaphore.acquire()
  • Condition.await() and many others in java.util.concurrent.*
  • SwingUtilities.invokeAndWait()

需要特别注意传统阻塞IO(blocking I/O)并不会抛出InterruptedException

以上提及的这些阻塞方法,如Object.wait()Thread.sleep()等方法被调用后,线程处理阻塞状态,这些方法会不断的轮询监听线程的interrupted标志位,发现其设置为true后,会停止阻塞并抛出InterruptedException异常,并且在抛出异常之后立即清除中断状态,也就是说在这时候在异常catch处理块中调用Thread.currentThread().isInterrupted()方法返回的仍然是false

抛出异常是为了唤醒线程,并且通知线程有中断信号需要处理,如何处理是由线程本身决定,下面也会提到2种常见的处理方式。

中断异常处理方式

1. 在catch子句中,调用Thread.currentThread.interrupt()来重新设置中断状态,因为在抛出InterruptedException异常后会清除中断状态,重置中断状态后可以让后面的代码通过判断Thread.currentThread().isInterrupted()结果而知道之前有中断发生过:

void () {
...
try {
sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
...
}

2. 更好的做法就是,不使用try来捕获这样的异常,让方法直接抛出,交由方法调用者来处理InterruptedException异常:

void () throws InterruptedException {
...
sleep(delay);
...
}

使用共享变量中断非阻塞状态的线程示例

注意共享变量的修饰符volatile,这是volatile最常见的用法之一,为了保证共享变量在不同线程中的内存可见性。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class ThreadInterruptExample2 extends Thread {
private static final Logger logger = LoggerFactory.getLogger(ThreadInterruptExample2.class);
volatile boolean stop = false;
public static void main(String args[]) throws Exception {
ThreadInterruptExample2 thread = new ThreadInterruptExample2();
logger.info("Starting thread...");
thread.start();
Thread.sleep(3000);
logger.info("Asking thread to stop...");
thread.stop = true; // reset share variable
Thread.sleep(3000);
logger.info("Stopping application...");
}
public void () {
// Every second check interrupt signal
while (!stop) {
logger.info("Thread is running...");
long time = System.currentTimeMillis();
/*
* Use a while loop simulation sleep method, here don't use sleep, otherwise they will be thrown on the obstruction
* Abnormal InterruptedException and exit the loop, so that the while detection of stop conditions will not be executed,
* Lost meaning.
*/
while ((System.currentTimeMillis() - time < 1000) && (!stop)) {
}
}
logger.info("Thread exiting under request...");
}
}

通过线程中断机制取消线程运行示例

import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.concurrent.TimeUnit.SECONDS;
public class ThreadInterruptClock extends Thread {
private static final Logger LOGGER = LoggerFactory.getLogger(ThreadInterruptClock.class);
public ThreadInterruptClock() { // The constructor
// Daemon thread: if set daemon true, java interpreter will exit and not trigger thread InterrutpedException
setDaemon(false);
}
public void () {
// The body of the thread
// While using the executor framework you do not know which thread instance is executing your code currently and hence the convention is to use Thread.currentThread.isInterrupted()
while (!Thread.currentThread().isInterrupted()) {
LOGGER.info(new DateTime().toString("yyyy-MM-dd HH:mm:ss Z")); // Print it out
try {
// Wait 1000 milliseconds
Thread.sleep(1000);
} catch (InterruptedException e) {
LOGGER.error("sleep interrutped!", e);
// Thread.sleep() and Object.wait() Throws:
// IllegalMonitorStateException - if the current thread is not the owner of the object's monitor.
// InterruptedException - if any thread interrupted the current thread before or while the current thread was waiting for a notification. The interrupted status of the current thread is cleared when this exception is thrown.
// IMPORTANT: reset interrupt mark
Thread.currentThread().interrupt(); // reset on interrupt status again for thread exit.
LOGGER.info("Quit on interrupt and clean interrupt status");
}
}
}
// Ask the thread to stop running.
public void pleaseStop() {
interrupt();
}
// This method demonstrates how to use the Clock class
public static void main(String[] args) throws InterruptedException {
ThreadInterruptClock c = new ThreadInterruptClock(); // Create a Clock thread
c.start(); // Start it
// Wait 10 seconds
SECONDS.sleep(5);
// Now stop the clock thread. We could also use c.interrupt()
c.pleaseStop(); // or c.interrupt();
LOGGER.info("=================finished===============");
}
}

通过ExecutorService控制线程中断示例

ExecutorService中会加入2个任务,其中一个线程会配合取消任务执行,另一个线程会无视中断信号,忽略异常并继续在后台执行。所以在主线程结束后,UncooperativeTask会继续在控制台中输出日志消息。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* http://wiki.deegree.org/deegreeWiki/deegree3/ProgrammingCooperativeThreads
*
* {@link CooperativeTask}会被正常取消任务, 调用过程:
* ExcecutorService.invokeAll()->FutureTask.cancel()->Thread.interrupt()
*
* {@link UncooperativeTask}在收到中断消息之后, 忽略了异常, 所以不会被取消执行, 任务会继续在后台执行。
*/
public class ThreadInterruptWithExecutorService {
private static final Logger LOGGER = LoggerFactory.getLogger(ThreadInterruptWithExecutorService.class);
public static void main(String[] args) throws InterruptedException {
ExecutorService execService = Executors.newCachedThreadPool();
LOGGER.info("Main thread: Using Executor to invoke an Uncooperative Task with a timeout of 1000 milliseconds.");
Collection<Callable<Object>> tasks = new ArrayList<Callable<Object>>();
tasks.add(new CooperativeTask());
tasks.add(new UncooperativeTask());
execService.invokeAll(tasks, 1000, TimeUnit.MILLISECONDS);
LOGGER.info("Main thread: exiting.");
execService.shutdown();
}
}
/**
* As one can see, the invokeAll-call in the main thread returns just after the timeout has occured (and the main thread exits).
* However, the thread that runs the uncooperative task keeps on running forever.
* In this case it is really simple to fix this behaviour: don't catch the InterruptedException (but use it to leave the loop).
*/
class CooperativeTask implements Callable<Object> {
private static final Logger LOGGER = LoggerFactory.getLogger(CooperativeTask.class);
public Object call() throws Exception {
long start = System.currentTimeMillis();
while (true) {
LOGGER.info("[email protected]" + (System.currentTimeMillis() - start) + ": Running.");
Thread.sleep(250);
}
}
}
/**
* As one can see, the methods of ExecutorService cannot cancel uncooperative code as well -- these methods just use Thread.interrupt() internally.
* If the code does not cope correctly with InterruptedExceptions (i.e. it just catches and ignores them), threads keep on going forever.
*/
class UncooperativeTask implements Callable<Object> {
private static final Logger LOGGER = LoggerFactory.getLogger(UncooperativeTask.class);
public Object call() throws Exception {
long start = System.currentTimeMillis();
while (true) {
LOGGER.info("[email protected]" + (System.currentTimeMillis() - start) + ": Running.");
try {
Thread.sleep(250);
} catch (InterruptedException e) {
LOGGER.info("[email protected]" + (System.currentTimeMillis() - start) + ": Caught InterruptedException, but ignoring it.");
}
}
}
}

References

  1. The interrupt mechanism of Thread
  2. Thread的中断机制
  3. 中断JAVA线程
  4. Programming cooperative threads