Java线程池的创建及参数理解

线程池的创建

使用阿里巴巴推荐的创建线程池的方式,通过ThreadPoolExecutor构造函数自定义参数创建。

private static final int CORE_POOL_SIZE = 5;
private static final int MAX_POOL_SIZE = 10;
private static final int QUEUE_CAPACITY = 100;
private static final Long KEEP_ALIVE_TIME = 1L;

public static void main(String[] args) {
    
    ThreadPoolExecutor executor = new ThreadPoolExecutor(
            CORE_POOL_SIZE,
            MAX_POOL_SIZE,
            KEEP_ALIVE_TIME,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(QUEUE_CAPACITY),
            new ThreadPoolExecutor.CallerRunsPolicy());
    
        for (int i = 0; i < size; i++) {
            int num = i;
            // 1. 第一种写法
            //executor.submit(new ThreadClass(num));
            // 2. 第二种写法
            //executor.submit(new Runnable() {
            //    @Override
            //    public void run() {
            //        System.out.println(Thread.currentThread().getName() + ", " + num);
            //    }
            //});
            // 3. lambda写法
            executor.submit(() -> {
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + ", " + num);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 一定要写在 finally中,防止异常计数器未减1
                    // 使latch的值减1,如果减到了0,则会唤醒所有等待在这个latch上的线程
                    countDownLatch.countDown();
                }

            });
        }
        //终止线程池
        executor.shutdown();
        try {
            executor.awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Finished all threads");
}

/**
 * Runnable 类
 */
static class ThreadClass implements Runnable {
    private int i;
    
    public ThreadClass(int i) {
        this.i = i;
    }
    
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " star " + i);
    
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        System.out.println(Thread.currentThread().getName() + " end " + i);
    }
}

创建线程池的构造方法的参数

java.util.concurrent.ThreadPoolExecutor#ThreadPoolExecutor:

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;
}

创建线程池一共有7个参数,从源码可知,corePoolSize和maximumPoolSize都不能小于0,且核心线程数不能大于最大线程数。

参数用途:

corePoolSize

核心线程数,判断任务没有超过核心线程数则直接创建线程,核心线程不会被回收,即使没有任务执行,也会保持空闲状态。

maximumPoolSize

最大线程数,如果线程池内部线程数已经超过核心线程数,且队列已满,没有超过最大线程数则继续创建新线程,超过最大线程数则出发拒绝策略handler。

keepAliveTime

当线程池内部的 临时线程 数量大于corePoolSize,则多出来的线程会在keepAliveTime时间之后销毁。

临时线程 是指当已经达到核心线程数、队列已满,且未达到最大核心数而创建的线程。

unit

keepAliveTime 的时间单位

workQueue

队列,当前线程数超过corePoolSize时,新的任务会处在等待状态,并存在workQueue中。

threadFactory

创建线程的工厂类,通常我们会自顶一个threadFactory设置线程的名称,这样我们就可以知道线程是由哪个工厂类创建的,可以快速定位。

handler

任务拒绝策略,当最大线程数已满,且任务队列已满,又有新的任务进来时,会调用handler拒绝策略来处理请求。

系统默认的拒绝策略有以下几种:

  • AbortPolicy:为线程池默认的拒绝策略,该策略直接抛异常处理。
  • DiscardPolicy:直接抛弃不处理。
  • DiscardOldestPolicy:丢弃队列中最老的任务。
  • CallerRunsPolicy:将任务分配给当前执行execute方法线程来处理。
  • 自定义拒绝策略,只需要实现RejectedExecutionHandler接口即可

我们还可以自定义拒绝策略,只需要实现RejectedExecutionHandler接口即可,友好的拒绝策略实现有如下:

将数据保存到数据,待系统空闲时再进行处理
将数据用日志进行记录,后由人工处理

为什么不建议使用Executors创建线程池?

JDK为我们提供了Executors线程池工具类,里面有默认的线程池创建策略,大概有以下几种:

FixedThreadPool:线程池线程数量固定,即corePoolSize和maximumPoolSize数量一样。
SingleThreadPool:单个线程的线程池。
CachedThreadPool:初始核心线程数量为0,最大线程数量为Integer.MAX_VALUE,线程空闲时存活时间为60秒,并且它的阻塞队列为SynchronousQueue,它的初始长度为0,这会导致任务每次进来都会创建线程来执行,在线程空闲时,存活时间到了又会释放线程资源。
ScheduledThreadPool:创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。

用Executors工具类虽然很方便,依然不推荐大家使用以上默认的线程池创建策略,阿里巴巴开发手册也是强制不允许使用Executors来创建线程池。

Executors工具类无非是把一些特定参数进行了封装,并提供一些方法供我们调用而已,我们并不能灵活地填写参数,策略过于简单,不够友好。

CachedThreadPool和ScheduledThreadPool最大线程数为Integer.MAX_VALUE,如果线程无限地创建,会造成OOM异常。

LinkedBlockingQueue基于链表的FIFO队列,是无界的,默认大小是Integer.MAX_VALUE,因此FixedThreadPool和SingleThreadPool的阻塞队列长度为Integer.MAX_VALUE,如果此时队列被无限地堆积任务,会造成OOM异常。


参考:

https://juejin.cn/post/6844903816513454094

本文链接: https://jianz.xyz/index.php/archives/325/

1 + 2 =
快来做第一个评论的人吧~