【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 中特有的方法

相关推荐
文丑颜不良啊4 小时前
LeetCode2221~2240题目解析
java·leetcode
秋饼4 小时前
Computer Use Agent Java 落地实战:感知-行动循环、执行器与安全围栏生产级实现
java·ai·技术分享·后端开发
文丑颜不良啊6 小时前
LeetCode2161~2180题目解析
java·leetcode
文丑颜不良啊8 小时前
LeetCode2141~2160题目解析
java·leetcode
LabVIEW开发8 小时前
LabVIEW 连接板:28 个端子怎么排,优先级怎么定
java·算法·labview
天天被压力9 小时前
【A股分钟级高频数据完全手册 #07】北交所历史分钟K线接口:京市Pro版分时批量
java·人工智能·python
天天被压力9 小时前
【A股分钟级高频数据完全手册 #08】股票日、分时数据接口对比:普通分时与Pro版差异实测
java·人工智能·python
特立独行的猫A9 小时前
AI 时代,我为什么推荐用 cj-tauri 开发桌面应用
java·后端·python
金銀銅鐵9 小时前
从一个类名到一张类图:用反射自动生成 PlantUML
java·后端·uml
nice_lcj52010 小时前
Java 并发容器入门:ConcurrentHashMap、CopyOnWriteArrayList、阻塞队列
java·面试·并发编程·阻塞队列·concurrenthashmap·copyonwritearraylist