Future cancel迷惑性boolean入参解析
Code皮皮虾 人气:0前言
当我们使用线程池submit
一个任务后,会返回一个Future
,而在Future
接口中存在一个cancel
方法,来帮助我们取消掉任务。
但是cancel
方法有一个boolean
类型的入参,比较迷惑,之前也了解过该入参true 和 false
的区别,但过一段时间之后就又忘了,遂写了本文进行记录,顺便了解下源码~
/** * Attempts to cancel execution of this task. This attempt will * fail if the task has already completed, has already been cancelled, * or could not be cancelled for some other reason. If successful, * and this task has not started when {@code cancel} is called, * this task should never run. If the task has already started, * then the {@code mayInterruptIfRunning} parameter determines * whether the thread executing this task should be interrupted in * an attempt to stop the task. * * <p>After this method returns, subsequent calls to {@link #isDone} will * always return {@code true}. Subsequent calls to {@link #isCancelled} * will always return {@code true} if this method returned {@code true}. * * @param mayInterruptIfRunning {@code true} if the thread executing this * task should be interrupted; otherwise, in-progress tasks are allowed * to complete * @return {@code false} if the task could not be cancelled, * typically because it has already completed normally; * {@code true} otherwise */ boolean cancel(boolean mayInterruptIfRunning);
上面是cancel
方法的接口定义,当然英文看着麻烦,咱直接翻译成看得懂的~
cancel
方法,会尝试取消任务的执行,但如果任务已经完成、已经取消或其他原因无法取消,则尝试取消任务失败。
如果取消成功,并且在取消时
- 该任务还未执行,那么这个任务永远不会执行。
- 如果该任务已经启动,那么会根据
cancel
的boolean
入参来决定是否中断执行此任务的线程来停止任务。
通过注释我们大致能了解到cancel
的一个作用,但是还不够细致,接下来我们通过源码解读详细的带大家了解一下~
FutureTask任务状态认知
首先,我们先了解下FutureTask
中对任务状态的定义
在使用线程池submit
后,实际上是返回的一个FutureTask
,而FutureTask
中对于任务定义了以下状态,并且在注释中,也定义了状态的流转过程~
/** * Possible state transitions: * NEW -> COMPLETING -> NORMAL * NEW -> COMPLETING -> EXCEPTIONAL * NEW -> CANCELLED * NEW -> INTERRUPTING -> INTERRUPTED */ private volatile int state; 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;
但是通过对上面状态定义的了解,我们可以发现,在FutureTask
中并没有一个表明任务处于执行中的一个状态!
直接看FutureTask
的run
方法源码
public void run() { if (state != NEW || !RUNNER.compareAndSet(this, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { // 执行任务 result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; // 执行异常 setException(ex); } if (ran) // 正常执行完毕 set(result); } } finally { //... 省略 } } protected void setException(Throwable t) { if (STATE.compareAndSet(this, NEW, COMPLETING)) { outcome = t; STATE.setRelease(this, EXCEPTIONAL); // final state finishCompletion(); } } protected void set(V v) { if (STATE.compareAndSet(this, NEW, COMPLETING)) { outcome = v; STATE.setRelease(this, NORMAL); // final state finishCompletion(); } }
通过上面源码,我们也能了解到
- 当任务正常执行完毕时,任务状态流转:
NEW -> COMPLETING -> NORMAL
- 任务执行异常时,任务状态流转:
NEW -> COMPLETING -> EXCEPTIONAL
所以,当任务刚创建,或者是任务在执行过程中,任务的状态都是NEW
cancel源码分析
此时再来分析cancel
源码
public boolean cancel(boolean mayInterruptIfRunning) { // NEW为新建或者运行态 // 1. 此时任务已经不是NEW,说明要么是完成要么是异常,取消不了,所以返回false // 2. 此时任务还是NEW,如果我们传入true,则CAS标记任务为INTERRUPTING,否则是CANCELLED // 防止并发取消任务,CAS只会有一个线程成功,其余线程失败 if (!(state == NEW && STATE.compareAndSet (this, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))) return false; try { // 传入true,则打断该任务的执行线程 if (mayInterruptIfRunning) { try { Thread t = runner; if (t != null) t.interrupt(); } finally { // 比较任务状态为INTERRUPTED STATE.setRelease(this, INTERRUPTED); } } } finally { finishCompletion(); } return true; }
通过对FutureTask
任务状态的认知,再结合对cancel
源码的分析
我们可以总结出以下结论
当任务已经完成或者异常时,无法取消任务
任务处于新建或者运行状态时
cancel
方法入参传入true
将任务状态NEW
-> INTERRUPTING
-> INTERRUPTED
,并打断执行该任务的线程
cancel
方法入参传入false
将任务状态NEW
-> CANCELLED
但有个问题,传入false
只是将状态从NEW
变成CANCELLED
嘛,这好像没啥用啊?
当然不是,此时我们需要再回头看看FutureTask
的run
方法
public void run() { if (state != NEW || !RUNNER.compareAndSet(this, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; // 执行异常 setException(ex); } if (ran) // 正常执行完毕 set(result); } } finally { //... 省略 } }
run
方法开头我们可以看到,如果任务的状态不是NEW
,那么会直接return
,不执行任务
那此时再想想传入false
将任务状态从NEW
-> CANCELLED
,是不是当任务还没有开始执行时,我们cancel(false)
就可以取消掉未执行的任务了~
总结
通过上面的源码解读,我们大致能了解了cancel
的机制,但是我们还是完善的总结一下
任务如果不是NEW
状态是不会执行的
cancel
取消任务会改变任务的状态
- 如果传入
true
, 则将任务状态NEW
->INTERRUPTING
->INTERRUPTED
,并打断执行该任务的线程 - 如果传入
false
,将任务状态NEW
->CANCELLED
传入false
只能取消还未执行的任务
传入true
,能取消未执行的任务,能打断正在执行的任务
扩展知识点
在cancel
源码中,我们可以看到finally
中会去调用finishCompletion
那么,finishCompletion
是干啥的呢?
private void finishCompletion() { // assert state > COMPLETING; for (WaitNode q; (q = waiters) != null;) { // 原子性将WAITERS设置为null if (WAITERS.weakCompareAndSet(this, q, null)) { // 遍历WAITERS,将阻塞的线程都唤醒 for (;;) { Thread t = q.thread; if (t != null) { q.thread = null; LockSupport.unpark(t); } WaitNode next = q.next; if (next == null) break; q.next = null; q = next; } break; } } // 扩展方法,交给自己实现 done(); callable = null; }
大家可以想想,当我们submit
一个任务时,一般情况下都会需要去获取他的返回值,会调用get
方法进行阻塞获取
在FutureTask
中,会维护一条链表,该链表记录了等待获取该任务返回值被阻塞的线程
在调用get
方法时,会将组装waiters
链表
所以,当我们取消一个任务时,是不是也应该去将阻塞等待获取该任务的所有线程进行唤醒,而finishCompletion
方法就是做这个事情的~
加载全部内容