线程池浅析 ThreadPoolExecutor FutureTask

前言

程序中使用 多线程 的场景,可以使用 线程池创建、管理 线程。使用 线程池 可以提高程序的性能,减少每个任务调用开销,并且允许我们针对具体的场景限制资源,比如 线程数、任务数

ThreadPoolExecutor

我们在程序中创建的 线程池 一般使用 ThreadPoolExecutor(包括 Executors 的相关方法创建的也是 ThreadPoolExecutor

ThreadPoolExecutor 允许我们指定 核心线程数、最大线程数、任务队列、线程空闲时间 等属性

接下来我们对 线程池 的重要属性进行解读

corePoolSize & maximumPoolSize

当我们调用 ThreadPoolExecutor#execute 方法给线程池分配一个任务

  • 如果此时 线程池 中运行的线程数小于 corePoolSize,则即便当前有线程空闲,线程池 也会创建一个新的线程来执行目标任务
  • 如果 线程池 中允许的线程数已经达到 corePoolSize,则目标任务会被添加到 任务队列,如果队列满了才会继续创建新的线程直到线程数达到 maximumPoolSize
  • corePoolSizemaximumPoolSize 属性由 ThreadPoolExecutor 的构造方法指定,同时也提供了 setCorePoolSizesetMaximumPoolSize 方法设置
  • 同时还提供了 prestartCoreThreadprestartAllCoreThreads 方法,允许提前创建对应数量(或者全部)的 core threads(核心线程)

threadFactory

执行任务的线程由我们指定的 ThreadFactory 创建,如果不指定,ThreadPoolExecutor 则默认由 Executors#defaultThreadFactory 方法返回的 DefaultThreadFactory 创建,该工厂创建的线程拥有同样的 线程组、优先级、守护状态

keepAliveTime

默认情况下,如果运行的线程数超过 corePoolSize,则超过的这部分线程如果空闲超过 keepAliveTime,便会被销毁,此举利于节省开销、管理资源

如果想同样对 corePoolSize 数量内的线程生效,则可以调用 allowCoreThreadTimeOut(true) 方法进行指定

允许空闲时长 keepAliveTime 同样可以在 构造方法 指定,也可以调用 setKeepAliveTime 方法设置

workQueue

任务队列,前文提到,当允许的线程数达到 corePoolSize 后,后续的任务会被放入 任务队列

ThreadPoolExecutor 的任务队列由 BlockingQueue 实现,基于 Queue 拓展了以下功能

  • 当队列满时,允许插入操作 put 阻塞
  • 当队列为空时,允许取出操作 take 阻塞
ThreadPoolExecutor 插入任务调用的 offer 方法,因为允许拒绝
	任务(见后文),取出任务使用的是 take 和 poll(阻塞版)
	关于常用的 BlockingQueue 的解读,可以参考下面几篇文章

【源码】JUC —— BlockingQueue ArrayBlockingQueue 浅析

【源码】JUC —— LinkedBlockingQueue 浅析

使用 Executors 类创建的线程池,默认使用 LinkedBlockingQueue

handler

如果任务队列满了,且运行的线程数已经达到 maximumPoolSize,则之后的任务交由 handler 处理,ThreadPoolExecutor 默认实现了 4RejectedExecutionHandler

CallerRunsPolicy

	public static class CallerRunsPolicy implements RejectedExecutionHandler {

        public CallerRunsPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

目标任务由 当前线程 执行执行,也相当于缓冲了任务的发放

AbortPolicy

	public static class AbortPolicy implements RejectedExecutionHandler {

        public AbortPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

直接抛出 RejectedExecutionException 异常

DiscardPolicy

	public static class DiscardPolicy implements RejectedExecutionHandler {

        public DiscardPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

啥也不干,相当于目标任务直接被 丢弃

DiscardOldestPolicy

	public static class DiscardOldestPolicy implements RejectedExecutionHandler {

        public DiscardOldestPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

把排在 队首 的元素 poll 出来(相当于丢掉了),然后继续执行当前任务(当然也并 不能 保证执行成功)

构造方法

依次解读了 ThreadPoolExecutor 的几个核心属性后,我们看看 ThreadPoolExecutor 提供的构造方法

	// 不指定 threadFactory 和 handler
	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

	// 不指定 handler
	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

	// 不指定 threadFactory 
	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

	// 全构造
	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  • corePoolSize,核心线程数
  • maximumPoolSize,最大线程数
  • keepAliveTime,允许线程空闲的最大时长
  • unitkeepAliveTime 的时间单位
  • workQueue,任务队列
  • threadFactory,如果没有提供,则使用默认的 DefaultThreadFactory
  • handler,如果没有指定,使用默认的 AbortPolicy,即抛出异常

FutureTask

我们看到 ThreadPoolExecutor#execute 方法接受一个 Runnable,同样 ThreadPoolExecutor 也接受 Callable,以获取异步执行的任务结果

ThreadPoolExecutor 的父类 AbstractExecutorService 实现了 ExecutorService 接口,ExecutorService 接口定义了 submit 相关方法,来支持异步任务结果的获取

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

Callable

Callable 类似与 Runnable,它提供了 call 方法,类似于 Runnable#run,但 call 方法如何做到被 Thread类 调用?

Future

Future 用来抽象 异步任务 的结果返回,提供了 get 方法 阻塞式 地获取异步任务的执行结果,同样提供了 取消任务执行、任务状态获取 等相关方法

public interface Future<V> {

    /**
     * 取消执行中的异步操作,但并不保证取消成功,如下场景会失败
     * 1)取消已经完成的操作
     * 2)已被取消过
     * 3)由于其他某些原因无法取消
     *
     * 如果取消的目标任务并未执行,那它必然不会再执行了
     * 但如果目标任务已经在执行了,则它是否中断取决于
     *      参数 mayInterruptIfRunning
     *
     * 执行该方法后调用 isDone 方法返回 true
     * 该方法如果返回 true 则调用 isCancelled 方法也返回 true
     */
    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    /**
     * Done 表示
     * 1)执行完成
     * 2)执行异常
     * 3)被取消
     */
    boolean isDone();

    // 阻塞直到异步任务返回结果
    V get() throws InterruptedException, ExecutionException;

    // 阻塞指定时间
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

RunnableFuture

public interface RunnableFuture<V> extends Runnable, Future<V> {

    void run();
}

RunnableFuture 接口就将 RunnableFuture 连接了起来

FutureTask

FutureTaskRunnableFuture 的实现类

  • 它是一个 Runnable,因此可以被 Thread 执行,换句话说,可以作为 异步任务线程池 执行
  • 它是一个 Future,因此支持 异步任务 的执行以及结果的 阻塞式 获取

我们来看看 FutureTask 的构造方法

	// 接受一个 Callable,直接赋值给属性 callable
	public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;
    }

	// 接受一个 Runnable,由 Executors#callable 创建对应的 callable 
	public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;
    }
  • 允许直接指定一个 callable
  • 允许指定一个 runnable 和返回结果 result,委托 Executors#callable 创建对应的 callable,代码如下
	public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }

	private static final class RunnableAdapter<T> implements Callable<T> {
        private final Runnable task;
        private final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
        
        // ...
    }

通过 适配器RunnableAdapter,将目标 Runnable 适配成对应的 Callable适配器设计模式 的经典使用场景

AbstractExecutorService

最后我们看看 ThreadPoolExecutor 的父类 AbstractExecutorService 如何实现 submit 方法,以实现 线程池对 异步任务 的执行和结果的 阻塞式 获取

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

    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;
    }

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

可以看到,submit 方法在将目标任务转换成 RunnableFuture 后交由 execute 方法执行,同时返回它以支持结果的 阻塞式 获取

	protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

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

方法 newTaskFor 就是创建对应的 RunnableFuture 并返回

示例

如此,我们对线程池 ThreadPoolExecutor 的属性和对异步任务的支持做了解读,给出示例 demo

public class ThreadPoolTest {

    /**
     * corePoolSize: 2
     * maximumPoolSize: 4
     * keepAliveTime: 1s
     * workQueue: 容量为 4 的 LinkedBlockingQueue
     * threadFactory: 默认的 DefaultThreadFactory
     * handler: 默认的 AbortPolicy,抛异常
     */
    ThreadPoolExecutor threadPoolExecutor
            = new ThreadPoolExecutor(
                    2
                    , 4
                    , 1
                    , TimeUnit.SECONDS
                    , new LinkedBlockingQueue<>(4)
                    // , (r, t) -> System.out.println("reject handle")
    );

    // 执行任务耗时 2s
    Runnable runnable = () -> {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };

    @Test
    public void test1() throws InterruptedException {

        // 如果调用此方法,在所有任务执行完成 1s 后,所有线程被销毁
        // threadPoolExecutor.allowCoreThreadTimeOut(true);

        /**
         * 执行 10 个任务
         * 第 1,2 个任务创建新的线程执行(耗时 2s)
         * 第 3,4,5,6 个任务放到 workQueue 中
         * 因为队列满了,第 7,8 个任务继续创建线程直到 maximumPoolSize(4)
         * 之后的第 9,10 个任务抛出异常(做了处理)
         */
        for (int i = 0; i < 10; i++) {
            try {
                threadPoolExecutor.execute(runnable);
            } catch (RejectedExecutionException re) {
                System.out.println("任务溢出了");
            }

            System.out.println("-----------------------");
            System.out.println("ActiveCount: " + threadPoolExecutor.getActiveCount());
            System.out.println("PoolSize: " + threadPoolExecutor.getPoolSize());
            System.out.println("QueuedCount: " + threadPoolExecutor.getQueue().size());
            System.out.println("-----------------------");
        }

        /**
         * 2s 后第一轮任务执行完成
         */
        TimeUnit.SECONDS.sleep(2);
        System.out.println("--------------------------");
        System.out.println("ActiveCount: " + threadPoolExecutor.getActiveCount());
        System.out.println("PoolSize: " + threadPoolExecutor.getPoolSize());
        System.out.println("QueuedCount: " + threadPoolExecutor.getQueue().size());
        System.out.println("--------------------------");

        /**
         * 又 2s 后第二轮任务执行完成,所有任务执行完
         */
        TimeUnit.SECONDS.sleep(2);
        System.out.println("--------------------------");
        System.out.println("ActiveCount: " + threadPoolExecutor.getActiveCount());
        System.out.println("PoolSize: " + threadPoolExecutor.getPoolSize());
        System.out.println("QueuedCount: " + threadPoolExecutor.getQueue().size());
        System.out.println("--------------------------");
        /**
         * 线程空闲 1s 后,超过 corePoolSize 的线程被回收
         */
        TimeUnit.SECONDS.sleep(1);
        System.out.println("---------------------------");
        System.out.println("ActiveCount: " + threadPoolExecutor.getActiveCount());
        System.out.println("PoolSize: " + threadPoolExecutor.getPoolSize());
        System.out.println("QueuedCount: " + threadPoolExecutor.getQueue().size());
        System.out.println("---------------------------");
        /**
         * 日志打印如下,结合回顾线程池的创建过程
         * --------------------------------------------
         * ActiveCount: 1
         * PoolSize: 1
         * QueuedCount: 0
         * --------------------------------------------
         * --------------------------------------------
         * ActiveCount: 2
         * PoolSize: 2
         * QueuedCount: 0
         * --------------------------------------------
         * --------------------------------------------
         * ActiveCount: 2
         * PoolSize: 2
         * QueuedCount: 1
         * --------------------------------------------
         * --------------------------------------------
         * ActiveCount: 2
         * PoolSize: 2
         * QueuedCount: 2
         * --------------------------------------------
         * --------------------------------------------
         * ActiveCount: 2
         * PoolSize: 2
         * QueuedCount: 3
        * --------------------------------------------
         * --------------------------------------------
         * ActiveCount: 2
         * PoolSize: 2
         * QueuedCount: 4
         * --------------------------------------------
         * --------------------------------------------
         * ActiveCount: 3
         * PoolSize: 3
         * QueuedCount: 4
         * --------------------------------------------
         * --------------------------------------------
         * ActiveCount: 4
         * PoolSize: 4
         * QueuedCount: 4
         * --------------------------------------------
         * 任务溢出了
         * --------------------------------------------
         * ActiveCount: 4
         * PoolSize: 4
         * QueuedCount: 4
         * --------------------------------------------
         * 任务溢出了
         * --------------------------------------------
         * ActiveCount: 4
         * PoolSize: 4
         * QueuedCount: 4
         * --------------------------------------------
         * --------------------------------------------
         * ActiveCount: 3
         * PoolSize: 4
         * QueuedCount: 0
         * --------------------------------------------
         * --------------------------------------------
         * ActiveCount: 0
         * PoolSize: 4
         * QueuedCount: 0
         * --------------------------------------------
         * --------------------------------------------
         * ActiveCount: 0
         * PoolSize: 2
         * QueuedCount: 0
         * --------------------------------------------
         */
    }

    volatile AtomicInteger atomicInteger = new AtomicInteger();

    // 阻塞 1s 后返回
    Callable<Integer> callable = () -> {
        TimeUnit.SECONDS.sleep(1);
        return atomicInteger.incrementAndGet();
    };

    @Test
    public void test2() throws ExecutionException, InterruptedException {
        for (int i = 0; i < 3; i++) {
            System.out.println(threadPoolExecutor.submit(callable).get());
        }

		// 结果
        // 1 2 3
    }
}

总结

本章节介绍了线程池 ThreadPoolExecutor 类,详细解读了 ThreadPoolExecutor 的属性

同时,对 FutureTask 体系的几个接口和类做了简单的解析,以说明 ThreadPoolExecutor 如何支持 异步任务 的执行和对执行结果的 阻塞式 获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值