往线程池提交(submit)任务,会把任务封装成FutureTask然后提交,返回future。
如果要取消任务,用future.cancel(), 在任务的状态不是NEW的情况下,cancel()方法没用,因为其他状态都表示任务已经完成或取消了。
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
注意:任务执行中的状态也是NEW。
如果任务还没开始,那么cancel后这个任务永不运行(周期性的任务以后不再运行)。如果任务已经开始执行,futureTask中会保持一个执行线程runner的引用,调用runner.interrupt(), 我们知道interrupt()只会让处于阻塞状态的线程抛出InterruptedException异常(中断状态清除),否则只会设置中断状态。
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
由于Thread.interrupt()不能真正中断线程,因此我们要在任务的run()中不时的去轮询Thraed.isInterrupted()来看任务是不是被取消了(当然也不是很准确,因为线程也可能是被别的原因中断,而不是future.cancel(),可以在future.cancel()的时候设置任务的状态,轮询这个状态就能判断任务是不是被取消了)。
被取消的任务调用future.get()会抛出CancellationException。
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
前面说了,取消任务通过future.cancel(),那么谁负责去调这个方法呢?主线程和任务执行线程都不合适去调,这里我是通过额外提交一个定时任务去实现,即在提交任务的时候,再提交一个延时的定时任务,延后的时间就是任务的超时时间。这样就会有2个任务了,可能会造成延时任务没执行或别的原因导致没取消掉或取消时间不准。
如果大家有更好的实现方式,也可以留言讨论。
网友评论