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

相关推荐
VaporGas7 分钟前
idea集成和使用Git指南
java·git·intellij-idea
阿乾之铭29 分钟前
spring MVC 拦截器
java·spring·mvc
码爸32 分钟前
flink 批量写clickhouse
java·clickhouse·flink
djgxfc34 分钟前
简单了解Maven与安装
java·maven
中文很快乐37 分钟前
springboot结合p6spy进行SQL监控
java·数据库·sql
丶白泽38 分钟前
重修设计模式-概览
java·设计模式
小电玩39 分钟前
谈谈你对Spring的理解
java·数据库·spring
五味香42 分钟前
C++学习,动态内存
java·c语言·开发语言·jvm·c++·学习·算法
无名之逆43 分钟前
计算机专业的就业方向
java·开发语言·c++·人工智能·git·考研·面试
爱棋笑谦1 小时前
二叉树计算
java·开发语言·数据结构·算法·华为od·面试