Java没有提供某种抢占式的机制来取消操作或结束线程。
- 可以通过协作式的中断机制来实现取消操作。
- 可以使用FutureTask和Executor框架,构建可以取消的任务和服务。
取消任务的原因有:
- 用户请求取消
- 有时间限制的操作
- 应用程序事件
- 错误
- 关闭
取消策略
- How - 其他代码如何(How)请求取消该任务
- When - 任务在何时检查是否有取消的请求
- What - 响应取消请求时应该执行哪些操作
取消任务的方式
1. 协作机制 - 设置一个“已请求取消”的标志
public class PrimeGenerator implements Runnable {
private static ExecutorService exec = Executors.newCachedThreadPool();
@GuardedBy("this") private final List<BigInteger> primes
= new ArrayList<BigInteger>();
private volatile boolean cancelled;//volatile域来保存一个取消状态,作为取消标识
public void run() {
BigInteger p = BigInteger.ONE;
while (!cancelled) {
p = p.nextProbablePrime();
synchronized (this) {
primes.add(p);
}
}
}
public void cancel() {
cancelled = true;
}
public synchronized List<BigInteger> get() {
return new ArrayList<BigInteger>(primes);
}
static List<BigInteger> aSecondOfPrimes() throws InterruptedException {
PrimeGenerator generator = new PrimeGenerator();
exec.execute(generator);
try {
SECONDS.sleep(1);
} finally {
generator.cancel();
}
return generator.get();
}
}
2.协作机制 - 中断
如果任务调用了一个阻塞方法,任务可能不会检查取消标识。
很多阻塞方法支持中断,如sleep,wait,join, 它们在响应中断请求的时候执行的操作有:清除中断状态,抛出InterruptedException。
所有可以使用中断来取消任务。
public class PrimeProducer extends Thread {
private final BlockingQueue<BigInteger> queue;
PrimeProducer(BlockingQueue<BigInteger> queue) {
this.queue = queue;
}
public void run() {
try {
BigInteger p = BigInteger.ONE;
while (!Thread.currentThread().isInterrupted())
queue.put(p = p.nextProbablePrime());
} catch (InterruptedException consumed) {
/* Allow thread to exit */
}
}
public void cancel() {
interrupt();
}
}
如何处理InterruptedException?
- 传递异常
- 恢复中断状态
如果不想或无法传递InterruptedException (如Runnable),可以再次调用Interrupt方法来恢复中断状态,让调用栈中的上层代码处理中断请求。
如果代码中没有调用可中断的阻塞方法,也可以通过任务代码检查当前线程中的中断状态来响应中断。
3.在专门的线程中中断任务
public class TimedRun2 {
private static final ScheduledExecutorService cancelExec = newScheduledThreadPool(1);
public static void timedRun(final Runnable r, long timeout, TimeUnit unit) throws InterruptedException {
class RethrowableTask implements Runnable {
private volatile Throwable t;
public void run() {
try {
r.run();
} catch (Throwable t) {
this.t = t;
}
}
void rethrow() {
if (t != null)
throw launderThrowable(t);
}
}
RethrowableTask task = new RethrowableTask();
final Thread taskThread = new Thread(task);
taskThread.start();
cancelExec.schedule(new Runnable() {
public void run() {
taskThread.interrupt();
}
}, timeout, unit);
taskThread.join(unit.toMillis(timeout));
task.rethrow();
}
}
4.通过Future来实现取消
public class TimedRun {
private static final ExecutorService taskExec = Executors.newCachedThreadPool();
public static void timedRun(Runnable r, long timeout, TimeUnit unit) throws InterruptedException {
Future<?> task = taskExec.submit(r);
try {
task.get(timeout, unit);
} catch (TimeoutException e) {
// task will be cancelled below
} catch (ExecutionException e) {
// exception thrown in task; rethrow
throw launderThrowable(e.getCause());
} finally {
// Harmless if task already completed
task.cancel(true); // interrupt if running
}
}
}
5.处理不可中断的阻塞
如Socket IO读写,
a) 可以同过override interrupt方法关闭socket来处理不可中断的阻塞。
b) 可以通过override newTaskFor方法,返回一个自定义的可以取消的task来关闭socket。
6.停止基于线程的服务
a)添加取消操作设置取消标识,关闭生产者和消费者服务。
b)使用线程池关闭生产者和消费者服务。
c)使用“毒丸”关闭生产者和消费者服务。
d)通过本地的Executor来处理批量任务(所有任务处理完返回)。
7.记录通过showdownnow方法取消的任务
8.处理非正常的线程终止
9.JVM关闭