Java ThreadPoolExecutor 使用及原理探究

1 篇文章 0 订阅

Java 中的线程池是并发框架当中运行场景最多的并发工具类,基本上需要异步或者并发执行的任务都可以使用线程池。相比开发人员直接使用手动创建线程,使用线程池可以有以下几个好处。

  • 降低资源消耗。通过重复利用已经创建的线程降低线程创建和销毁造成的消极。
  • 提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行
  • 提高线程的可管理性。在 Java 中线程和操作系统里面的线程是一一对就的。所以线程是稀缺,如果无限地创建线程,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是要做到合理利用线程池,必须对其实现原理了如指掌。

我们知道了使用线程池的好处,下面我们来看一下使用线程池的典型场景:

  • 服务器接受到大量请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率
  • 实际上,在开发中,如果需要创建 5 个以上的线程,那么就可以使用线程池来管理

1、线程池使用 Demo

下面就写一个简单的 demo 来演示线程池是如何使用的。

public class RunnableDemo {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(new Task());
        }
        executorService.shutdown();
    }
    private static class Task implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }

}
  • Executors.newFixedThreadPool(10) 创建工作线程数为 10 的线程池
  • executorService.execute(runnable),提交 100 线程,打印当前线程名
  • executorService.shutdown(),关闭线程池

2、线程池类结构

我们首先来看一下线程池的类结构体系。
在这里插入图片描述

  • Executor:一个执行提交的 Runnable 任务的对象。这接口提供了一种方法来解耦任务提交每个任务如何运行的机制,包括线程的细节使用、日程安排等通常使用 Executor而不是显式地创建线程。例如,而不是为每个线程调用new Thread(new(RunnableTask())).start()
  • ExecutorService:ExecutorService 可以被关闭,这将导致它拒绝新任务。提供了两种不同的方法关闭 ExecutorServiceshutdown()方法将允许以前提交的任务在以前执行终止,shutdownNow 方法阻止等待从开始的任务和尝试停止当前正在执行的任务。在终止时,执行人没有正在执行的任务等待执行的任务,不能提交新的任务。一个未使用的ExecutorService应该关闭以允许回收资源。
  • AbstractExecutorService:提供了 ExecutorService 接口的默认实现执行方法。这个类实现了 submit()invokeAny()invokeAll()方法使用由 newTaskFor() 返回的 RunnableFuture,默认值为到这个包中提供的 FutureTask 类。例如, submit(Runnable) 的实现创建了一个关联被执行的 RunnableFuture 和返回RunnableFuture 实现,而不是 FutureTask。子类可以重写 newTaskFor 方法。
  • ThreadPoolExecutor:线程池解决两个不同的问题:它们通常在执行大量时提供改进的性能异步任务,由于减少了每个任务的调用开销,它们提供了一种限制和管理资源的方法,包括执行任务集合时使用的线程。
    每个 ThreadPoolExecutor 也维护一些基本的统计信息,例如已完成任务的数量。

3、线程池的核心参数

我们可以通过 ThreadPoolExecutor 来创建一个线程池。

new ThreadPoolExecutro(corePoolSizie, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler);

创建一个线程池需要输入几个参数,如下:

  1. corePooloSize(线程池基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的 prestartAllCoreThreads() 方法,线程池会提交创建并启动所有基本线程。

  2. runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几人阻塞队列。

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
  • LindedBlockingQueue: 是一个基于链表结构的阻塞队列,此队列按 FIFO 排序元素,吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPoll() 使用了这个队列。
  • SynchronousQueue: 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。
  • PriorityBlockingQueue:一个具有优先级的无限阻塞队列
  1. maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没什么效果。

  2. ThreadFactory: 用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。使用开源框架 guava 提供的 ThreadFactoryBuilder 可以快速给线程池里的线程设置有意义的名字,代码如下。

new ThreadFactoryBuilder().newNameFormat("XXX-task-%d").build();
  1. RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是 AbortPolicy, 表示无法处理新任务时抛出异常。线程池默认有以下 4 种策略。
  • AbortPolicy : 这种策略会直接抛出一个 RejectedExecutionException 异常。
  • DiscardPolicy: 这种策略是丢弃线程,当线程池处理能力不足时,就会把新添加的线程默默的丢弃掉。不会得到通知,使用者也不会知道线程被丢弃掉了。
  • DiscardOldestPolicy : 这种策略是丢失最老的,当线程池处理能力不足时,它会把最老的存在时间最久的任务丢弃掉。以便来提交刚刚提交的任务。这种策略比 DiscardPolicy 策略人性化一点。
  • CallerRunsPolicy : 这种策略谁提交的这个任务谁就去执行这个任务,这种策略有以下两点好处:一个是和之前的策略不一样,它不会丢弃掉任务,不会业务损失;另一个是它可以让提交的速度降低下来,因为主线程一直提交任务,线程池能力不足够的时候就会让主线程执行任务。执行任务是需要时间的,所以提交速度会降低下来。

当然,也可以根据应用场景需要来实现 RejectedExecutionHandler 接口自定义策略。如记录日志或持久化存储不能处理的任务。

  1. keepAliveTime(线程活动保持时间): 线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。

  2. TimeUnit(线程活动保持时间的单位): 可选的单位有天(DAYS)、小时(HOURS)、分钟(MINTUES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒).

4、几种不同的线程池

线程池通常使用工厂类 Executors 来创建,Executors 可以创建 4 种类型的线程池:SingleThreadExecutorFixedThreadPoolCachedThreadPoolScheduledThreadPoolExecutor

  • FixedThreadPool:创建使用固定线程数的线程池 适用用为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。
ExecutorService newFixedThreadPool(int nThreads);

ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
  • SingleThreadExecutor:创建使用单个线程的的线程池。适用于需要保证顺序地执行的各个任务;并且在任意时间点,不会有多个线程是活动的场景。
ExecutorService newSingleThreadExecutor()

ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
  • CachedThreadPool。:创建一个会根据需要创建新线程的线程池。它是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。
ExecutorService newCachedThreadPool();

ExecutorService newCachedThreadPool(ThreadFactory threadFactory);
  • ScheduledThreadPoolExecutor:创建一个周期执行任务的线程池,通常使用工厂类 Executor 来创建。可以创建一个线程或者若干个线程的 ScheduledThread。
    下面是创建固定个数线程的 ScheduledThreadPoolExecutor 的 API。
ScheduledExecutorService newScheduledThreadPool(int corePoolSize);

ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory);

ScheduledThreadPoolExecutor适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数据的应用场景。下面是 Executor 提供的创建单个线程的 SingleThreadScheduledExecutor的 API

ScheduledExecutorService newSingleThreadScheduledExecutor();

ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory);

SingleThreadScheduledExecutor 适用用需要单个后台线程执行周期任务,同时需要保证顺序执行各个任务的应用场景

下面是这几个线程池几个比较重要参数的对比:

ParameterFixedThreadPoolCachedThreadPoolSingleThreadPoolScheduledThreadPool
corePoolSizeconstructor-arg0Constructor-arg1
maxPoolSizesame as corePoolSizeIntege.MAX_VALUEIntege.MAX_VALUE1
keepAliveTime0 seconds60 seconds00 seconds

5、线程池源码分析

下面我们来分析一下线程池内部执行原理。

5.1 线程池状态

以下是线程池里面的核心变量。

ThreadPoolExecutor.java

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;
    
    // Packing and unpacking ctl
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }

其中 AtomicInteger 类型的变量 ctl 的功能非常强大:利用低 29 位表示线程池中线程数,通过 高 3 位 表示线程池的运行状态:

  • RUNNING-1 << COUNT_BITS,即高 3 位为 111,该状态的线程池会接收新任务,并处理阻塞队列中的任务;
  • SHUTDOWN0 << COUNT_BITS,即高3位为 000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;
  • STOP1 << COUNT_BITS,即高3位为 001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
  • TIDYING2 << COUNT_BITS,即高3位为 010.中文是简洁,理解了中文就容易理解这个状态了。所有任务都已终止, workeCount 为零时,线程会转换到 TIDYING 状态,并将运行 terminate()钩子方法。
  • TERMINATED3 << COUNT_BITS,即高3位为 011terminate() 运行完成 ;

5.2 线程池执行

Java 线程池提供了两种不同的方式来执行任务:

    /**
     * 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);

这种方式的任务需要实现 java.lang.Runnable ,没有返回值无法判断任务是否成功执行。

    /**
     * Submits a value-returning task for execution and returns a
     * Future representing the pending results of the task. The
     * Future's {@code get} method will return the task's result upon
     * successful completion.
     *
     * <p>
     * If you would like to immediately block waiting
     * for a task, you can use constructions of the form
     * {@code result = exec.submit(aCallable).get();}
     *
     * <p>Note: The {@link Executors} class includes a set of methods
     * that can convert some other common closure-like objects,
     * for example, {@link java.security.PrivilegedAction} to
     * {@link Callable} form so they can be submitted.
     *
     * @param task the task to submit
     * @param <T> the type of the task's result
     * @return a Future representing pending completion of the task
     * @throws RejectedExecutionException if the task cannot be
     *         scheduled for execution
     * @throws NullPointerException if the task is null
     */
    <T> Future<T> submit(Callable<T> task);

这法方式任务需要实现 java.util.concurrent.Callable 是可以获取到任务执行后的返回值的。并且在 submit(Callable) 方法最终会执行到 execute(Runnable command)

5.3 线程池原码分析

下面我们就来分析一下 ThreadPoolExecutor#execute 的执行源码。

ThreadPoolExecutor#execute

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

第一步,ctl 记录了线程池状态和线程数 ,workerCountOf方法根据ctl的低29位,得到线程池的当前线程数。首先会判断如果工作的线程数少于核心线程数,就会创建一个线程, addWorker 有两个请求参数,一个是任务对象,另外一个参数是 boolean 值,当这个值是 true 就会判断运行的线程数是否大于核心线程数。如果这个值是 false 的话就会判断运行的线程数是否大于最大线程数。如果添加任务成功的话就会直接返回。

第二步,首先会判断当前线程池是否在运行状态,如果当前线程池在运行中,这个时候运行的任务已经大于等于核心线程数,所以会把任务添加到阻塞队列当中。这个时候有可能线程池被停止了,所以它需要再次判断当前线程池是否正处于运行当中。如果当前线程池没有运行的话会就会把当前任务删除,并执行拒绝策略。workerCountOf(recheck) == 0 如果当前工作的线程数量为 0 的话,因为线程有可能会抛异常,导致这个线程停止。所以核心工作的线程有可能会减少。这个时候就需要创建 Worker 来执行线程。因为这个时候已经把任务提交到队列当中,这个时候如果工作 Worker 数为 0 的话就没有线程来执行任务。

第三步,这个时候工作线程数大于等于核心线程数,并且可能是线程池没有在运行状态或者阻塞队列已经添加执行的任务已经满了。 这个时候就需要增加工作 Worker 直到达到线程池的最大线程数。

第四步,在添加工作 Worker 的时候会返回一个 boolean 值,如果当前的运行的工作 Worker 已经等于最大线程数的时候就会执行拒绝策略。

在这里插入图片描述

5.3.1 addWork

添加任务逻辑如下:

ThreadPoolExecutor#addWord

        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
  • 首先会获取当前线程池的运行状态,如果当前运行状态大于等于 SHUTDOWN,或者当前的阻塞队列是空的直接返回。
  • 然后获取当前线程池正在工作的线程个数,如果大于等于低 29 位或者大于满足 core ? corePoolSize : maximumPoolSize 这个值就直接返回
  • 尝试使用 CAS 添加当前的工作线程个数,如果添加成功就跳出当前循环
  • 获取当前线程池的运行状态如果和刚进行的时候不一致,就跳到外层的循环继续执行

这个是添加工作任务的前个部分,下面我们来分析一下添加工作任务的另外一个部分。

ThreadPoolExecutor#addWord

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;

工作线程通过 Worker 实现了并且继承了并发工具类 ReentrantLock 可以线程安全的可工作线程添加到 HashSet 当中。在工作线程添加成功之后就会运行。

  • Work 实现了 Runnable 接口,然后通过线程池传入的线程工厂创建线程, Work 本身可以以任务的形式添加到执行线程中去。
  • Work 继承了同步工具类 AbstractQueuedSynchronizer, 可以使用里面的同步方法
  • 把提交的任务通过构造器传入 Work 当中,当任务执行时候会运行里面的任务

因为 Work 实现了 java.lang.Runnable ,所以当 Work 运行的时候。其实是执行它的 run() 方法。最终会调用 runWorker() 方法。

5.3.2 runWorker

ThreadPoolExecutor#runWorker

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

runWorker 方法是线程池执行的核心:

  • 通过调用 Work#unlock 方法,允许当前的任务被 interrupts
  • 获取 Work 当中的任务 firstTask,并且设置当前 WorkfirstTask 为空,以便 Work 执行完当前任务可以执行其它任务。当前任务会先执行 Work#lock方法,并且最终会调用 Work#unlock 方法
  • 在任务执行的前后会调用线程池任务执行前的 beforeExecute 和 任务执行后的 afterExecute 这两个钩子方法。
  • 从阻塞队列里面获取任务执行,如果获取到的任务不为空,如果获取到的任务不为空就会执行,否则会一直阻塞
5.3.3 getTask

下面我们来看一下线程池从阻塞队列里面获取任务

ThreadPoolExecutor#getTask

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

整个 getTask 操作在自旋下完成:

  • workQueue#take:如果阻塞队列为空,当前线程会被挂起等待;当队列中有任务加入时,线程被唤醒,take方法返回任务,并执行;
  • workQueue#poll:如果在keepAliveTime时间内,阻塞队列还是没有任务,则返回null;

所以,线程池中实现的线程可以一直执行由用户提交的任务。

5.3.4 Callable 与 Future

上面讲的是 Runnable 类型的任务,这个任务有一个缺点就是没有返回值,下面我们就来讲一下有返回值的任务。线程池还可以执行实现了 java.util.concurrent.Callable,这个任务就可以有返回值。下面我们来比较一下 Runnable 与 Callable 这两个任务。

  • 它们都可以在线程中被执行。
  • Runnable 任务没有返回值, Callable 有返回值
  • Runnable 抛出的线程父线程无法处理, Callable 线程的异常可以在返回值当中获取
  • Future#get 方法会阻塞父线程直接 Callable 任务执行完成或者发生异常
public class CallableDemo {

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        Future<String> future = executorService.submit(new CallTask());

        String result = future.get();

        System.out.println(result);

        executorService.shutdown();
    }
    private static class CallTask implements Callable<String> {
        @Override
        public String call() {
            return "this is a callable demo";
        }
    }

}
5.3.5 ThreadPoolExecutor#submit

调用线程池 ThreadPoolExecutor#submit 会调用它的父类方法 AbstractExecutorService#submit

.AbstractExecutorService#submit

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

这时的 Callable 任务会包装成 FutureTask

FutureTask.java

    /**
     * The run state of this task, initially NEW.  The run state
     * transitions to a terminal state only in methods set,
     * setException, and cancel.  During completion, state may take on
     * transient values of COMPLETING (while outcome is being set) or
     * INTERRUPTING (only while interrupting the runner to satisfy a
     * cancel(true)). Transitions from these intermediate to final
     * states use cheaper ordered/lazy writes because values are unique
     * and cannot be further modified.
     *
     * 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 的所有状态,刚开始的时候是 NEW,它有几下如何状态机转换。

  • NEW -> COMPLETING -> NORMAL
  • NEW -> COMPLETING -> EXCEPTIONAL
  • NEW -> CANCELLED
  • NEW -> INTERRUPTING -> INTERRUPTED

FutureTask 同时也实现了 Runnable 接口,在执行 submit(Runnable task) 方法的时候调用了 execute(Runnable command) ,最终其实是调用了 java.util.concurrent.FutureTask#run 方法。

5.3.6 FutureTask#get

下面就是 FutureTask#get 的具体代码。

FutureTask#get

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

当调用 FutureTask#get 会阻塞父线程,直到返回结果。具体实现如下:

    /**
     * Awaits completion or aborts on interrupt or timeout.
     *
     * @param timed true if use timed waits
     * @param nanos time to wait, if timed
     * @return state upon completion
     */
    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
            else if (q == null)
                q = new WaitNode();
            else if (!queued)
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos);
            }
            else
                LockSupport.park(this);
        }
    }

上面的逻辑如下:

  • 如果当前线程被设置了中断标记,抛出 InterruptedException
  • 如果 FutureTask 当前的状态大于 COMPLETING ,说明任务已经执行完成,则直接返回;
  • 如果当前状态等于 COMPLETING,说明任务已经执行完,这时线程只需通过 Thread#yield 方法让出 cpu 资源,等待 state 变成 NORMAL;
  • 通过 WaitNode 类封装当前线程,并通过 UNSAFE 添加到 waiters 链表;
  • 最终通过 LockSupport 的 park 或 parkNanos 挂起线程;
5.3.7 FutureTask#run

下面就是有返回值 Callable 任务最终执行的代码。

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);
        }
    }
  • 如果当前 FutureTask 的状态不是NEW 直接返回
  • 调用 Callable#call 方法
  • 如果调用过程中发生异常直接通过 setException 方法保存异常
  • 如果调用完成通过 set 方法保存响应结果

FutureTask#setException

    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

FutureTask#set

    protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }

FutureTask#setExceptionFutureTask#set,都会通过UnSAFE 修改 FutureTask 的状态,并执行finishCompletion 方法通知主线程任务已经执行完成;

FutureTask#finishCompletion

    /**
     * Removes and signals all waiting threads, invokes done(), and
     * nulls out callable.
     */
    private void finishCompletion() {
        // assert state > COMPLETING;
        for (WaitNode q; (q = waiters) != null;) {
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                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; // unlink to help gc
                    q = next;
                }
                break;
            }
        }

        done();

        callable = null;        // to reduce footprint
    }
  • 执行 FutureTask#get 方法时,会把主线程封装成 WaitNode节点并保存在 waiters 链表中;
  • FutureTask 任务执行完成后,通过 UNSAFE设置 waiters 的值,并通过 LockSupport#unpark方法唤醒主线程;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值