JAVA中线程池ThreadPoolExecutor的使用

目录

线程池

线程池的配置

corePoolSize

maximumPoolSize

keepAliveTime

unit

workQueue

threadFactory

默认方式手动创建线程池

[ThreadFactoryBuilder 类创建线程池](#ThreadFactoryBuilder 类创建线程池)

handler

四种内置的拒绝策略

‌AbortPolicy(中止策略)

CallerRunsPolicy(调用者运行策略)

DiscardPolicy(丢弃策略)

‌DiscardOldestPolicy(丢弃最旧策略)


线程池

线程池,就是有一定数量线程的池子,池子中的线程可以多次使用,这可以减少创建和销毁线程的次数,从而达到节约运行时间和系统资源的目的。ThreadPoolExecutor是Java中的一个ExecutorService。

线程池的配置

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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

corePoolSize

核心线程数,默认为1。

该参数的设置主要看使用场景,在CPU密集型的场景中使用,corePoolSize = CPU 核数 + 1;在IO密集型的场景中使用,corePoolSize = CPU 核数 * 2

在提交一个任务到线程池中时,如果池中线程数量小于核心线程数,线程池会创建一个线程来执行任务,即使有空闲线程存在也会创建新线程。

默认情况下,线程池只有在有任务时才会创建线程,prestartAllCoreThreads()方法,可以实现预先创建并启动好所有核心线程

java 复制代码
    /**
     * Starts all core threads, causing them to idly wait for work. This
     * overrides the default policy of starting core threads only when
     * new tasks are executed.
     *
     * @return the number of threads started
     */
    public int prestartAllCoreThreads() {
        int n = 0;
        while (addWorker(null, true))
            ++n;
        return n;
    }

maximumPoolSize

线程池可创建的最大线程数,默认为Integer.MAX_VALUE

一般设置为和核心线程数一样

如果队列已满,线程池将创建新的线程来处理任务,直到线程池中的线程数量达到设置的最大线程数量。

keepAliveTime

空闲线程存活时间,当线程个数大于corePoolSize时,其他额外空闲线程的存活时间,默认为60s

设置此参数的目的是释放多余的线程资源,一个非核心线程在空闲等待新任务时,如果超过keepAliveTime设置的时间,这个线程就会被销毁

如果此参数设置为0,那么所有的线程将一直不会被销毁

在任务比较多并且任务执行很快的情况下,这个参数可以适当设置的更大一些,避免线程频繁销毁创建带来的资源浪费。

setKeepAliveTime()方法可以单独设置线程存活时间

java 复制代码
    /**
     * Sets the time limit for which threads may remain idle before
     * being terminated.  If there are more than the core number of
     * threads currently in the pool, after waiting this amount of
     * time without processing a task, excess threads will be
     * terminated.  This overrides any value set in the constructor.
     *
     * @param time the time to wait.  A time value of zero will cause
     *        excess threads to terminate immediately after executing tasks.
     * @param unit the time unit of the {@code time} argument
     * @throws IllegalArgumentException if {@code time} less than zero or
     *         if {@code time} is zero and {@code allowsCoreThreadTimeOut}
     * @see #getKeepAliveTime(TimeUnit)
     */
    public void setKeepAliveTime(long time, TimeUnit unit) {
        if (time < 0)
            throw new IllegalArgumentException();
        if (time == 0 && allowsCoreThreadTimeOut())
            throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
        long keepAliveTime = unit.toNanos(time);
        long delta = keepAliveTime - this.keepAliveTime;
        this.keepAliveTime = keepAliveTime;
        if (delta < 0)
            interruptIdleWorkers();
    }

unit

参数keepAliveTime的时间单位,默认为秒,TimeUnit 类型,可选枚举:

  • TimeUnit.NANOSECONDS(纳秒)
  • TimeUnit.MICROSECONDS(微秒)
  • TimeUnit.MILLISECONDS(毫秒)
  • TimeUnit.SECONDS(秒)
  • TimeUnit.MINUTES(分钟)
  • TimeUnit.HOURS(小时)
  • TimeUnit.DAYS(天)

workQueue

用于保存等待执行任务的队列,当线程池中的线程都在忙碌时,新提交的任务将被添加到工作队列中等待执行。常见的工作队列类型有有界队列(如ArrayBlockingQueue)和无界队列(如LinkedBlockingQueue),还有一种不存储元素的阻塞队列(SynchronousQueue

threadFactory

线程工厂,用来创建线程,是一个接口,只有一个方法,即创建一个线程,其定义为:

java 复制代码
/**
 * An object that creates new threads on demand.  Using thread factories
 * removes hardwiring of calls to {@link Thread#Thread(Runnable) new Thread},
 * enabling applications to use special thread subclasses, priorities, etc.
 *
 * <p>
 * The simplest implementation of this interface is just:
 *  <pre> {@code
 * class SimpleThreadFactory implements ThreadFactory {
 *   public Thread newThread(Runnable r) {
 *     return new Thread(r);
 *   }
 * }}</pre>
 *
 * The {@link Executors#defaultThreadFactory} method provides a more
 * useful simple implementation, that sets the created thread context
 * to known values before returning it.
 * @since 1.5
 * @author Doug Lea
 */
public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

默认方式手动创建线程池

java 复制代码
// 这里使用Executors.defaultThreadFactory()方式创建
ExecutorService executorService = new ThreadPoolExecutor(5,5,60,TimeUnit.SECONDS,new ArrayBlockingQueue(100));

// 调用类的方法定义
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

ThreadFactoryBuilder 类创建线程池

可以使用 Guava(一个由Google开发的开源Java库)库的 ThreadFactoryBuilder 类来构建线程池

java 复制代码
//setNameFormat()可以指定线程组名称,方便后期debug
ThreadFactory namedFactory = new ThreadFactoryBuilder().setNameFormat("thread-list-review-record-service-impl").build();
ExecutorService executorService = new ThreadPoolExecutor(3, 3, 1, TimeUnit.MINUTES,
                new SynchronousQueue<>(), namedFactory, new ThreadPoolExecutor.CallerRunsPolicy());

handler

拒绝策略,默认是AbortPolicy,会抛出异常

当线程池和队列都满了时,表示线程池已经饱和,此时需要确定如何处理新提交来的任务。

拒绝策略只有在使用了有界队列,并且maximumPoolSize有上限时才有可能被触发。如果队列是无界队列,则任务会一直往队列中放置。如果队列是有界队列,maximumPoolSize设置的无限大,就会不停地创建新的线程,占满CPU和内存,最终可能导致程序崩溃。

四种内置的拒绝策略

ThreadPoolExecutor 是 Java 中一个强大的线程池实现,它提供了四种内置的拒绝策略来处理当线程池无法再接受新任务时的情况

AbortPolicy(中止策略)

默认的拒绝策略,当线程池无法处理新任务时,会抛出 RejectedExecutionException 异常,适用于不能容忍任务被丢弃或延迟执行的业务场景

CallerRunsPolicy(调用者运行策略)

当线程池无法处理新任务时,会直接在调用者线程中运行被拒绝的任务。如果调用者线程正在执行一个任务,则可能会创建一个新线程来执行被拒绝的任务。适用于那些可以容忍任务在调用者线程中执行的业务场景,但需要注意调用者线程的性能影响。

DiscardPolicy(丢弃策略)

当线程池无法处理新任务时,会静默丢弃无法处理的任务,不抛出异常,也不执行。该策略会导致数据丢失,一般是不会使用的。

DiscardOldestPolicy(丢弃最旧策略)

当线程池无法处理新任务时,会丢弃队列中最旧的任务(即等待时间最长的任务),然后尝试重新提交当前任务。使用该种策略,同样会导致数据丢失,并且会打乱任务的执行顺序,产生依赖关系错乱等问题。

除了四种内置的拒绝策略,还可以通过实现 RejectedExecutionHandler 接口来创建自定义的拒绝策略,根据实际情况进行处理,例如记录日志、发送告警或将任务放入其他队列等待后续处理。

相关推荐
工业互联网专业21 分钟前
基于springboot+vue的高校创新创业课程体系的设计与实现
java·vue.js·spring boot·毕业设计·源码·课程设计
明 庭40 分钟前
在Alpine这小破车里塞进Nginx?
java·nginx·eureka
孟芳芳1 小时前
JAVA 冒泡排序算法
java·算法
快乐非自愿1 小时前
C++中的各种锁
java·c++·算法
Gauss松鼠会1 小时前
GaussDB事务和并发控制机制
java·数据库·sql·华为云·gaussdb
三次拒绝王俊凯1 小时前
java求职学习day11
java·开发语言·学习
Q_27437851091 小时前
django基于Python的电影推荐系统
java·后端·python·django
ZERO空白1 小时前
spring task使用
java·后端·spring
xiao--xin2 小时前
LeetCode100之括号生成(22)--Java
java·开发语言·算法·leetcode·回溯
java1234_小锋2 小时前
Redis是单线程还是多线程?
java·数据库·redis