Java线程池相关

JDK线程池相关一

jdk中将计算任务(task)和计算任务执行本身解耦。

基础接口与类

与计算任务相关的两个接口和

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

两个接口的区别从代码和注释中显而易见。

Callable带返回值,且可能抛异常(受检);Runnable不带返回值,且不抛异常(受检)。

而任务的执行则由另一个接口Executor

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

注意,这个接口只描述了可以执行任务,并没有线程池的概念,当然线程池是一定可以执行任务的,因此线程池需要实现该接口。

为了追踪任务的异步计算,比如提交完一个任务之后需要知道任务是否已完成,或者是取消该任务,jdk提供了一个名为Future的接口

public interface Future<V> {

   /**
    * 取消与之关联的任务。如果任务已经完成或者已经取消,或者是因为某些原因不能取消则返回false
    * 如果任务还未开始,且取消成功,则该任务永远不会再被执行
    * 如果任务已经开始,则输入参数mayInterruptIfRunning决定是否采用中断的方式叫停任务
    */
    boolean cancel(boolean mayInterruptIfRunning);

    /**
     * 任务完成前被取消 则返回true
    */
    boolean isCancelled();

    /**
    * 返回任务是否完成
    * 任务完成包括以下情况
    * 正常结束、异常或者取消
    * 以上任何一种情况下都返回true
    */
    boolean isDone();

    /**
    * 阻塞到任务结束,返回任务执行结果
    */
    V get() throws InterruptedException, ExecutionException;
    
    /**
    * 等待一段时间,还没结束则抛超时异常
    */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

至此,我们了解了三个概念:

  • 任务:Callable或者Runnable实现,定义任务做什么
  • 任务执行器:Executor,用于执行任务
  • 任务状态:Future,用于追踪任务的执行,任务的一次执行

但是这几部分是怎么联系起来的呢?线程池在哪里呢?

先回答第二个问题。

jdk提供了另一个接口,该接口定义了执行器的一系列行为(方法),那就是ExecutorService。

public interface ExecutorService extends Executor {

    /**
    * 该方法会以一种比较平滑的方式关闭执行器:
    * 1.已经提交的任务会被执行(但不保证执行完毕)
    * 2.不再接收新的任务
    * 如果该执行器已经关闭了,再调用此方法没有任何作用
    * 该方法不会等到已经提交的任务执行完毕
    */
    void shutdown();

    /**
     * 比较粗暴的关闭执行器,直接试图中止所有的任务,挂起所有等待执行的任务
     * 返回所有等待执行的任务
     * 该方法不会等待正在执行中的任务执行完毕
     */
    List<Runnable> shutdownNow();

    /**
     * 执行器已经被关闭时返回true
     *
     */
    boolean isShutdown();

    /**
     * 关闭执行器后如果所有的任务都已执行完毕则返回true
     * 这意味着该方法只可能在调用shutdown或者shutdownNow后返回true
     */
    boolean isTerminated();

    /**
     * 执行器关闭后阻塞至所有的任务执行完毕或者超时、中断
     * 当执行器已经终止时返回true;超过指定时间还没终止则返回false
     */
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
     * 提交一个带返回值的任务给执行器,并返回一个Future对象用于跟踪任务的执行
     */
    <T> Future<T> submit(Callable<T> task);

    /**
     * 提交一个没有返回值的任务给执行器,并返回一个Futrue对象用于跟踪任务的执行
     * 任务执行完毕后使用get可以得到指定的result
     */
    <T> Future<T> submit(Runnable task, T result);

    /**
     * 提交一个没有返回值的任务给执行器,并返回一个Futrue对象用于跟踪任务的执行
     * 任务执行完毕后使用get返回null
     */
    Future<?> submit(Runnable task);

    /**
     * 提交多个任务,该方法是阻塞的,只有所有的任务完成后才返回与这些任务关联的Future列表
     * Future列表中的每一个对象调用isDone都返回true
     * 任务正常结束或者抛异常才成为完成
     * 输入的列表在执行该方法时被修改时,则返回的结果未定义
     */
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

    /**
     * 指定执行的时长,到时间后任务要么执行完毕要么超时
     * 所有的Future对象调用isDone都返回true
     * 返回前,所有未能完成的任务都会被取消掉
     */
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    /**
     * 只要任意一个任务完成,则返回该任务的返回值,其他未完成的任务都会被取消
     */
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    /**
     * 只要任意一个任务在指定的时间点前返回,则返回该任务的返回值,其他未完成的任务都会被取消
     * 如果没有任何一个任务及时完成,则抛出超时异常
     */
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

再来回答第一个问题。

jdk提供了一个接口

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

该接口直接就将任务和任务追踪关联起来了。

接下来我们看看执行器的实现。jdk中提供了一个执行器(其实是线程池)的抽象类,该类实现了其上层接口的很多方法,留给一些必要的方法给子类去实现。

public abstract class AbstractExecutorService implements ExecutorService

这里我们只看看这个类的submit方法

public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

可以看到这两个方法是对ExecutorService接口中方法的实现。

可以看到这两个方法中都是将任务封装成一个RunnableFuture对象,然后扔给executor执行,最后返回该RunnableFuture对象。

来看看这个newTaskFor方法

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

返回了一个我们尚未提及的一个类示例:FutureTask。显然这个FutureTask是RunnableFuture的实现类。

目前为止,还有一个疑问:既然任务是执行器来执行的,任务的状态是通过Future来查询的,那Future中的状态是什么时候设置的呢?那就得看FutrueTask的源码了。

我们挑选它的一个构造函数来看

public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

可以看到FutureTask包含了传递进去的Callable。而AbstractExecutorService中的execute执行的实际上是包含Callable对象的FutureTask。因此执行器(Executor)执行的是FutureTask的run方法。对于执行器来说,它并不知道FutureTask内部的状态,它只负责调用FutureTask的run方法,该run方法会完成FutureTask的状态变更。

FutureTask有一个成员变量

 private volatile Thread runner;

这个成员变量是用于执行FutureTask的线程。对于线程池而言,这个线程就是线程池分配给它的。来看看run方法

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         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 {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

这个run方法执行时其实已经是由某一个线程来执行了,对于线程池而言就是分配给它的线程。而第一个if是为了将当前线程赋值给成员变量runner。而cancel方法中会尝试中断该线程。

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         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 {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

FutureTask我会单独写一篇源码分析,此处大致了解即可。

回到AbstractExecutorService的两个submit方法,注意到execute方法,该方法其实是Executor接口中的方法,抽象类并没有实现该方法,这说明需要子类去实现。

目前为止还没有出现过线程池的概念。其实jdk中的线程池就是用AbstractExecutorService来实现的。该类就是ThreadPoolExecutor。看名字就能知道是线程池执行器。这个类会另写文章单独分析。

总结

jdk线程池的设计将任务(静态),任务的一次执行(动态)以及任务的执行解耦。定义了不同的接口分别去完成这些事情。

任务由Callable或者Runnable来定义,只定义了任务需要做什么,这是个静态的概念。

任务的一次执行则由Future来表示,通过Future可以知道与之关联的任务的执行状态,这是个动态的概念。

任务的执行则由执行器来完成,执行器只负责执行任务,并不直接对外提供查询某个任务是否完成的功能(由Future来提供)。

任务被封装成FutureTask后交由执行器执行,FutureTask对任务(Callable或者Runnable)进行封装,加上了一些状态(未执行、执行中、执行完成等)。执行器执行FutureTask的run方法最终会调用任务的run或者call方法,在调用任务的run或者call的前后,FuturTask负责更改自身的状态。因此,对于执行器来说并不关心任务的状态,它只负责调用FutureTask的run方法,至于FutureTask的run方法中怎么处理,那就是FutureTask自己的事情了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容

  • 下面是我自己收集整理的Java线程相关的面试题,可以用它来好好准备面试。 参考文档:-《Java核心技术 卷一》-...
    阿呆变Geek阅读 14,785评论 14 507
  • 前言:线程是稀缺资源,如果被无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,合理的使用线程池对线程进行统一...
    SDY_0656阅读 710评论 0 1
  • Java-Review-Note——4.多线程 标签: JavaStudy PS:本来是分开三篇的,后来想想还是整...
    coder_pig阅读 1,641评论 2 17
  • 一对一培训新代理,领悟能力非常快! 现在最让我开心,并有信心的事情,就是做团队!
    Seven_七柒琪阅读 78评论 0 0
  • 梦里,一首黑白的歌唱着我干涩的喉咙不经意的痴心疯又在心底骂着天空一无所有的明 鱼缸里,一条名叫可乐的鱼死在了昨天,...
    立黄昏阅读 1,234评论 57 58