Java 线程池

线程池规范

《阿里巴巴 Java 开发手册》对线程池的规范创建和使用做了定义。

由线程池提供线程

在这里插入图片描述
即不要按照以下方式创建线程:

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        // do something by a thread
    }
});

如果在 IntelliJ IDEA 中安装了 Alibaba Java Coding Guidelines 插件,那么显示创建线程的语句会被提示:
在这里插入图片描述
提示内容如下:
不要显式创建线程,请使用线程池。
线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
    .setNameFormat("demo-pool-%d").build();
ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

singleThreadPool.execute(()-> System.out.println(Thread.currentThread().getName()));
singleThreadPool.shutdown();

使用 ThreadPoolExecutor 创建线程池

在这里插入图片描述
使用 ThreadPoolExecutor 创建线程池可以自定义各参数,比使用 Executors 更加灵活,而且通过合理设置参数可以避免出现 OOM。
如果在 IDEA 中使用 Executors 创建线程池,会被阿里的插件提示
在这里插入图片描述
提示内容如下:
手动创建线程池,效果会更好哦。
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPoolSingleThreadPool
  允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool
  允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

  • Positive example 1:

    //org.apache.commons.lang3.concurrent.BasicThreadFactory
    ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
        new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
    
  • Positive example 2:

    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
            .setNameFormat("demo-pool-%d").build();
    
    //Common Thread Pool
    ExecutorService pool = new ThreadPoolExecutor(5, 200,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
    
    pool.execute(()-> System.out.println(Thread.currentThread().getName()));
    pool.shutdown();//gracefully shutdown
    
  • Positive example 3:

    <bean id="userThreadPool"
        class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="10" />
        <property name="maxPoolSize" value="100" />
        <property name="queueCapacity" value="2000" />
    
    <property name="threadFactory" value= threadFactory />
        <property name="rejectedExecutionHandler">
            <ref local="rejectedExecutionHandler" />
        </property>
    </bean>
    
    //in code
    userThreadPool.execute(thread);
    

线程池参数

ThreadPoolExecutor 构造方法

线程池的参数说明以 J.U.C(java.util.concurrent) 提供的 ThreadPoolExecutor 为例。首先来看 ThreadPoolExecutor 的构造方法。

/**
 * Creates a new {@code ThreadPoolExecutor} with the given initial
 * parameters.
 *
 * @param corePoolSize the number of threads to keep in the pool, even
 *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
 * @param maximumPoolSize the maximum number of threads to allow in the
 *        pool
 * @param keepAliveTime when the number of threads is greater than
 *        the core, this is the maximum time that excess idle threads
 *        will wait for new tasks before terminating.
 * @param unit the time unit for the {@code keepAliveTime} argument
 * @param workQueue the queue to use for holding tasks before they are
 *        executed.  This queue will hold only the {@code Runnable}
 *        tasks submitted by the {@code execute} method.
 * @param threadFactory the factory to use when the executor
 *        creates a new thread
 * @param handler the handler to use when execution is blocked
 *        because the thread bounds and queue capacities are reached
 * @throws IllegalArgumentException if one of the following holds:<br>
 *         {@code corePoolSize < 0}<br>
 *         {@code keepAliveTime < 0}<br>
 *         {@code maximumPoolSize <= 0}<br>
 *         {@code maximumPoolSize < corePoolSize}
 * @throws NullPointerException if {@code workQueue}
 *         or {@code threadFactory} or {@code handler} is null
 */
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;
}

参数简介

各参数说明

序号名称类型含义
1corePoolSizeint核心线程池大小
2maximumPoolSizeint最大线程池大小
3keepAliveTimelong线程最大空闲时间
4unitTimeUnit时间单位
5workQueueBlockingQueue<Runnable>线程等待队列
6threadFactoryThreadFactory线程创建工厂
7handlerRejectedExecutionHandler拒绝策略

参数之间的关系
在这里插入图片描述

参数详解

受限于硬件、内存和性能,我们不可能无限制的创建任意数量的线程,因为每一台机器允许的最大线程是一个有界值。也就是说 ThreadPoolExecutor 管理的线程数量是有界的。线程池就是用这些有限个数的线程,去执行提交的任务。然而对于多用户、高并发的应用来说,提交的任务数量非常巨大,一定会比允许的最大线程数多很多。为了解决这个问题,必须要引入排队机制,或者是在内存中,或者是在硬盘等容量很大的存储介质中。 ThreadPoolExecutor 只支持任务在内存中排队,通过 BlockingQueue 暂存还没有来得及执行的任务。
任务的管理是一件比较容易的事,复杂的是线程的管理,这会涉及线程数量、等待/唤醒、同步/锁、线程创建和死亡等问题。ThreadPoolExecutor 与线程相关的几个成员变量是:keepAliveTimeallowCoreThreadTimeOutpoolSizecorePoolSizemaximumPoolSize,它们共同负责线程的创建和销毁。

corePoolSize 核心线程池大小

线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。这里需要注意的是:在刚刚创建 ThreadPoolExecutor 的时候,线程并不会立即启动,而是要等到有任务提交时才会启动,除非调用了 prestartCoreThread / prestartAllCoreThreads 事先启动核心线程。再考虑到 keepAliveTimeallowCoreThreadTimeOut 超时参数的影响,所以没有任务需要执行的时候,线程池的大小不一定是 corePoolSize

maximumPoolSize 最大线程池大小

线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于 maximumPoolSize,那么会创建新的线程来执行任务。这里值得一提的是 largestPoolSize,该变量记录了线程池在整个生命周期中曾经出现的最大线程个数。为什么说是曾经呢?因为线程池创建之后,可以调用 setMaximumPoolSize() 改变运行的最大线程的数目。

poolSize 当前线程池大小

线程池中当前线程的数量,当该值为0的时候,意味着没有任何线程,线程池会终止;同一时刻,poolSize 不会超过 maximumPoolSize

keepAliveTime 线程最大空闲时间

  • keepAliveTime 的单位是纳秒,1s=1000000000ns,1秒等于10亿纳秒。
  • keepAliveTime 是线程池中空闲线程等待工作的超时时间。
    当线程池中线程数量大于 corePoolSize(核心线程数量)或设置了 allowCoreThreadTimeOut(是否允许空闲核心线程超时)时,线程会根据 keepAliveTime 的值进行活性检查,一旦超时便销毁线程。否则,线程会永远等待新的工作。

workQueue 任务队列

用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
  • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按 FIFO (先进先出) 排序元素,吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool() 使用了这个队列
  • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool()使用了这个队列。
  • PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

threadFactory 线程工厂

如果这里没有传参的话,会使用 Executors 中的默认线程工厂类 DefaultThreadFactory

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
public static ThreadFactory defaultThreadFactory() {
    return new DefaultThreadFactory();
}
/**
 * The default thread factory
 */
static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

DefaultThreadFactory 的构造方法可知,此工厂创建的线程的前缀格式如下: pool-1-thread-1,第一个数字 1 代表线程池的序号,第二个数字 1 代表线程的序号,且创建的线程为非守护线程(用户线程),优先级为 Thread.NORM_PRIORITY(值为 5)。

但阿里的规范建议使用第三方的线程工厂,设置有意义的线程名:

ExecutorService THREAD_POOL = new ThreadPoolExecutor(3, 5,
                1, TimeUnit.SECONDS, new LinkedBlockingQueue<>(32), 
                new ThreadFactoryBuilder().setNameFormat("guava-pool-%d").build());

ThreadFactoryBuilder 是第三方库 guava 中的一个线程工厂类,要使用 guava 需要引入其 Maven 依赖:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.2-jre</version>
</dependency>

handler 拒绝策略

拒绝任务:拒绝任务是指当线程池里面的线程数量达到 maximumPoolSizeworkQueue 队列已满的情况下被尝试添加进来的任务。
handler: 线程池对拒绝任务的处理策略。在 ThreadPoolExecutor 里面定义了 4 种 handler 策略,分别是

  1. CallerRunsPolicy :这个策略重试添加当前的任务,会自动重复调用 execute() 方法,直到成功。
  2. AbortPolicy :对拒绝任务抛弃处理,并且抛出异常。
  3. DiscardPolicy :对拒绝任务直接无声抛弃,没有异常信息。
  4. DiscardOldestPolicy :对拒绝任务不抛弃,而是抛弃队列里面等待最久的一个线程,然后把拒绝任务加到队列。

查看 ThreadPoolExecutor 中关于拒绝策略的源码,可以发现以上四种策略都实现了 RejectedExecutionHandler 接口。

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    /**
     * Creates a {@code CallerRunsPolicy}.
     */
    public CallerRunsPolicy() { }

    /**
     * Executes task r in the caller's thread, unless the executor
     * has been shut down, in which case the task is discarded.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

/**
 * A handler for rejected tasks that throws a
 * {@code RejectedExecutionException}.
 */
public static class AbortPolicy implements RejectedExecutionHandler {
    /**
     * Creates an {@code AbortPolicy}.
     */
    public AbortPolicy() { }

    /**
     * Always throws RejectedExecutionException.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     * @throws RejectedExecutionException always
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}

/**
 * A handler for rejected tasks that silently discards the
 * rejected task.
 */
public static class DiscardPolicy implements RejectedExecutionHandler {
    /**
     * Creates a {@code DiscardPolicy}.
     */
    public DiscardPolicy() { }

    /**
     * Does nothing, which has the effect of discarding task r.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}

/**
 * A handler for rejected tasks that discards the oldest unhandled
 * request and then retries {@code execute}, unless the executor
 * is shut down, in which case the task is discarded.
 */
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    /**
     * Creates a {@code DiscardOldestPolicy} for the given executor.
     */
    public DiscardOldestPolicy() { }

    /**
     * Obtains and ignores the next task that the executor
     * would otherwise execute, if one is immediately available,
     * and then retries execution of task r, unless the executor
     * is shut down, in which case task r is instead discarded.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

查看 ThreadPoolExecutor 源码,可以发现默认的 handlerAbortPolicy

/**
 * The default rejected execution handler
 */
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}

参考博客

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java线程池是一种用于管理和复用线程的机制,它可以提高多线程应用程序的性能和效率。线程池中的线程可以被重复使用,避免了频繁创建和销毁线程的开销。 在Java中,线程池可以通过`ExecutorService`接口来创建和管理。线程池中的线程可以执行提交给它的任务,并且可以根据需要自动创建新的线程或销毁闲置的线程。 嵌套线程池是指在一个线程池创建另一个线程池。这种情况通常发生在需要处理一些复杂的任务,其中每个任务本身也需要使用线程池来执行。 下面是一个示例代码,演示了如何在Java中嵌套使用线程池: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class NestedThreadPoolExample { public static void main(String[] args) { // 创建外层线程池 ExecutorService outerThreadPool = Executors.newFixedThreadPool(5); // 提交任务给外层线程池 outerThreadPool.execute(() -> { // 创建内层线程池 ExecutorService innerThreadPool = Executors.newFixedThreadPool(3); // 提交任务给内层线程池 innerThreadPool.execute(() -> { // 内层线程池执行的任务 System.out.println("Inner thread pool task executed"); }); // 关闭内层线程池 innerThreadPool.shutdown(); }); // 关闭外层线程池 outerThreadPool.shutdown(); } } ``` 在上面的示例中,我们首先创建了一个外层线程池`outerThreadPool`,它使用`Executors.newFixedThreadPool()`方法创建了一个固定大小的线程池。然后,我们向外层线程池提交了一个任务,该任务在执行时创建了一个内层线程池`innerThreadPool`,并向内层线程池提交了一个任务。最后,我们分别关闭了内层线程池和外层线程池

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值