【JavaEE】Executors中常见线程池工厂方法及其使用

参考文章:Java Executors类的9种创建线程池的方法及应用场景分析 - 威哥爱编程 - SegmentFault 思否

本文将介绍Executors中常见一些工厂方法。Executors中的工厂方法其实就是对ThreadPoolExecutor的封装,方便使用。


线程池的执行流程

  1. 创建线程池,并用线程工厂给线程起个好名字。
  2. 有任务过来,线程池才会创建线程,并执行任务。
  3. 后续还有任务过来,所有核心线程没有空闲的,那么就放入任务队列中等待,直到有空闲的线程。
  4. 任务队列如果是有界的,如果任务超过了队列的大小,此时就会创建临时线程来执行任务。
  5. 如果临时线程也到达了最大值,并且都没有空闲的。此时就会执行四种拒绝策略
    1. AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
    2. CallerRunsPolicy 让调用者运行任务
    3. DiscardPolicy 放弃本次任务
    4. DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
  6. 当任务都被完成后,临时线程到达指定的最大空闲时间后就会被释放。

可以看到ThreadPoolExecutor的最全构造方法

java 复制代码
    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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

提交任务

这里提交任务都是线程池中提交任务的方法。

java 复制代码
        // 执行任务
        void execute(Runnable command);

        // 提交任务 task,用返回值 Future 获得任务执行结果
        <T> Future<T> submit(Callable<T> task);

        // 提交 tasks 中所有任务
        <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)throws InterruptedException;

        // 提交 tasks 中所有任务,带超时时间
        <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;

        // 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
        <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;

        // 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
        <T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

处理异常

线程池中的异常处理可以在线程执行内部捕捉,还可以用Futrue,在拿到结果后处理。

主动捕捉

java 复制代码
        ExecutorService pool = Executors.newFixedThreadPool(2);
        pool.submit(() -> {
            try {
                System.out.println("hello");
                int i = 1 / 0;
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

Futrue捕捉

java 复制代码
        ExecutorService pool = Executors.newFixedThreadPool(1);
        Future<Boolean> f = pool.submit(() -> {
            System.out.println("hello");
            int i = 1 / 0;
            System.out.println("world");
            return true;
        });
        try {
            f.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

newFixedThreadPool

创建固定大小的线程池。

这里核心线程数==最大线程数,也就是说不会创建临时线程,所以也不需要超时时间。

工作队列是LinkedBlockingQueue,可以看成是无界的(实际上是int的最大值)可以看到下面的源码。

java 复制代码
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

    /**
     * Creates a {@code LinkedBlockingQueue} with a capacity of
     * {@link Integer#MAX_VALUE}, initially containing the elements of the
     * given collection,
     * added in traversal order of the collection's iterator.
     *
     * @param c the collection of elements to initially contain
     * @throws NullPointerException if the specified collection or any
     *         of its elements are null
     */
    public LinkedBlockingQueue(Collection<? extends E> c) {
        this(Integer.MAX_VALUE);
        final ReentrantLock putLock = this.putLock;
        putLock.lock(); // Never contended, but necessary for visibility
        try {
            int n = 0;
            for (E e : c) {
                if (e == null)
                    throw new NullPointerException();
                if (n == capacity)
                    throw new IllegalStateException("Queue full");
                enqueue(new Node<E>(e));
                ++n;
            }
            count.set(n);
        } finally {
            putLock.unlock();
        }
    }
java 复制代码
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

该线程池适合运用于任务量已知,相对来说比较耗时的工作。因为它可以保证任务以固定的线程数量并行执行,同时避免了线程数量的无限制增长。然而,由于线程池的大小是固定的,如果任务提交的速率超过了线程池的处理能力,可能会导致任务在队列中等待较长时间。因此,在使用 newFixedThreadPool 时,需要根据任务的特性和预期的负载来合理设置 nThreads 的值。


newCachedThreadPool

创建临时线程干活。

核心线程数是0,最大线程数是int的最大值 --> 临时线程数是int的最大值

临时线程最大的空闲时间是60秒。

工作队列是SynchronousQueue,其特点是没有容量,没有线程来取任务是放不进取任务的。

java 复制代码
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

这种设计使得 newCachedThreadPool 非常适合处理大量短生命周期的任务,因为它可以动态地调整线程数量以适应任务负载的变化。然而,由于它可以创建无限多的线程,如果没有适当的任务队列来控制任务的数量,可能会导致资源耗尽。因此,在使用 newCachedThreadPool 时,需要谨慎考虑任务的特性和系统的资源限制。

适用于执行大量短期异步任务,尤其是任务执行时间不确定的情况。例如,Web服务器处理大量并发请求,或者异步日志记录。


newSingleThreadExecutor

创建一个固定线程为1的线程池。

希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。

java 复制代码
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

与单独创建一个线程执行任务的区别:
自己创建一个单线程串行执行任务,如果任务执行失败而终止 那么没有任何补救措施 ,而线程池还 会新建一 个线程,保证池的正常工作。
与Executors.newFixedThreadPool(1)的区别:
Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改。FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改。FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法

相关推荐
IT枫斗者枫哥10 小时前
MyBatis一对多分页:LIMIT 20,为什么凑不齐20个订单?
java·数据库
代码方舟10 小时前
Java数据工程:利用天远全网运营商三要素优化线上实名认证合规体验
java·人工智能
BBmmo10 小时前
我的 Java 学习笔记 · 第 7 篇:泛型与通配符
java
独泪了无痕10 小时前
Hutool之RandomUtil:随机数生成的终极利器
java·后端
花开路口10 小时前
深入理解 Java/Kotlin 协变与逆变
java·kotlin
运行时异常10 小时前
【WMS 仓储系统集成 AI Agent 实战】第 7 讲:Vue3 前端工程化——Token 刷新锁、Markdown 渲染踩坑与权限体系
java
她的男孩10 小时前
开放接口限流从 20 改到 200 还是每分钟 20 次:拆完防重放+幂等+限流,我找到 5 个静默失效的坑
java·后端·架构
天天被压力10 小时前
【Python 量化取数指南 #13】Python 把行情落库:sqlite 一键存,回测随用随取
java·人工智能·python
天天被压力10 小时前
【Python 量化取数指南 #14】Python 清洗行情数据:复权停牌对齐,回测不翻车
java·人工智能·python
独泪了无痕10 小时前
开发利器Hutool之MapUtil的使用
java·后端