Java线程池相关

JDK线程池相关一

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

基础接口与类

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

public interface Callable {
    /**
     * 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 Runnable is used
     * to create a thread, starting the thread causes the object's
     * run method to be called in that separately executing
     * thread.
     * 

* The general contract of the method run 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 {

   /**
    * 取消与之关联的任务。如果任务已经完成或者已经取消,或者是因为某些原因不能取消则返回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 shutdownNow();

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

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

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

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

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

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

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

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

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

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

再来回答第一个问题。

jdk提供了一个接口

public interface RunnableFuture extends Runnable, Future {
    /**
     * 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 ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

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

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

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

来看看这个newTaskFor方法

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

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

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

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

public FutureTask(Callable 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 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 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自己的事情了。

你可能感兴趣的:(Java线程池相关)