Future cancel迷惑性boolean入参解析

 更新时间:2023年02月28日 16:49:13   作者:Code皮皮虾  
这篇文章主要为大家介绍了Future cancel迷惑性boolean入参解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

当我们使用线程池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方法,会尝试取消任务的执行,但如果任务已经完成、已经取消或其他原因无法取消,则尝试取消任务失败。

如果取消成功,并且在取消时

  • 该任务还未执行,那么这个任务永远不会执行。
  • 如果该任务已经启动,那么会根据cancelboolean入参来决定是否中断执行此任务的线程来停止任务。

通过注释我们大致能了解到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中并没有一个表明任务处于执行中的一个状态!

直接看FutureTaskrun方法源码

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嘛,这好像没啥用啊?

当然不是,此时我们需要再回头看看FutureTaskrun方法

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方法就是做这个事情的~

以上就是Future cancel迷惑性boolean入参解析的详细内容,更多关于Future cancel boolean入参的资料请关注脚本之家其它相关文章!

相关文章

  • springboot更新配置Swagger3的一些小技巧

    springboot更新配置Swagger3的一些小技巧

    今天给大家分享springboot更新配置Swagger3的方法,大家需要注意Swagger3版本需要引入依赖,具体示例代码参考下本文
    2021-07-07
  • Java实现任务超时处理方法

    Java实现任务超时处理方法

    任务超时处理是比较常见的需求,Java中对超时任务的处理有两种方式,在文中给大家详细介绍,本文重点给大家介绍Java实现任务超时处理方法,需要的朋友可以参考下
    2019-06-06
  • 在springboot中添加mvc功能的正确姿势讲解

    在springboot中添加mvc功能的正确姿势讲解

    这篇文章主要介绍了在springboot中添加mvc功能的正确姿势,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • mybatis postgresql 批量删除操作方法

    mybatis postgresql 批量删除操作方法

    PostgreSQL是一种特性非常齐全的自由软件的对象-关系型数据库管理系统(ORDBMS),这篇文章主要介绍了mybatis postgresql 批量删除操作,需要的朋友可以参考下
    2020-02-02
  • Java程序中实现调用Python脚本的方法详解

    Java程序中实现调用Python脚本的方法详解

    这篇文章主要介绍了Java程序中实现调用Python脚本的方法,结合实例形式分析了eclipse环境中使用Java调用Python脚本的相关操作技巧与注意事项,需要的朋友可以参考下
    2018-03-03
  • 从Springboot项目中下载文件的具体过程

    从Springboot项目中下载文件的具体过程

    最近在做一个临时的项目,APP端在检测到程序有更新时,需要去后台下载新的安装包,接下来通过本文给大家分享从Springboot项目中下载文件的具体过程,感兴趣的朋友一起看看吧
    2021-07-07
  • Java中Builder模式的实现详解

    Java中Builder模式的实现详解

    在设计模式中对Builder模式的定义是用于构建复杂对象的一种模式,所构建的对象往往需要多步初始化或赋值才能完成。下面这篇文章主要给大家介绍了在Java各个版本中Builder模式实现的相关资料,文中介绍的非常详细,需要的朋友可以参考学习。
    2017-05-05
  • crawler4j抓取页面使用jsoup解析html时的解决方法

    crawler4j抓取页面使用jsoup解析html时的解决方法

    crawler4j对response没有指定编码的页面,解析成乱码,很让人烦恼,下面给出解决方法,需要的朋友可以参考下
    2014-04-04
  • Java并发之异步的八种实现方式

    Java并发之异步的八种实现方式

    本文主要介绍了Java并发之异步的八种实现方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • springboot如何连接两个数据库(多个)

    springboot如何连接两个数据库(多个)

    这篇文章主要介绍了springboot如何连接两个数据库(多个),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01

最新评论