前言
当咱们运用线程池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
办法就是做这个工作的~
结尾
我是 皮皮虾 ,会在今后的日子里跟咱们一起学习,一起进步!
觉得文章不错的话,能够在 关注我,或许是我的公众号——JavaCodes。