【java笔记】线程池ThreadPoolExecutor

===============================================

 2022/6/23_第2次修改                       ccb_warlock

 

更新说明:

2022/6/23:

1.补充了“为什么不允许通过Executors创建线程”的内容;

===============================================

在java中使用线程池,是在一个项目接口的业务后新加一个接口转发,但是该转发不能因为转发业务的响应慢而阻塞该项目接口。

之前看《阿里巴巴Java开发手册》在“并发控制”中写明了“线程池不允许使用Executors创建,而是通过ThreadPoolExecutor的方式创建,这样的处理方式能让编写代码的工程师更加明确线程池的运行规则,规避资源耗尽的风险”,于是在项目中开始使用ThreadPoolExecutor创建线程。

应用了一段时间后,发现ThreadPoolExecutor在不同的应用场景需要做不同的配置,于是我针对这块做了试验和整理。

 


一、为什么不允许通过Executors创建线程 

根据《阿里巴巴Java开发手册》提到的Executors,学习后发现其实java的工具类提供了2套线程池的方案,我理解是Executors对ThreadPoolExecutor进行一层封装,可以减少学习上手就用。

但是由于JDK 1.8之前的Executors创建出来的线程池队列长度都是Integer的最大值,大量创建任务时会导致队列中塞入过多的任务,从而造成内存溢出情况,这获取就是手册标记“强制”不允许使用Executors创建线程池的原因吧。

不过JDK 1.8开始有了一种新的线程池newWorkStealingPool是基于ForkJoinPool创建的,以后有应用后再来补充。

 

Executors有newFixedThreadPool、newWorkStealingPool、newSingleThreadExecutor、newCachedThreadPool、newScheduledThreadPool5种方式创建线程池。

 

1.1 newFixedThreadPool

基于ThreadPoolExecutor。

创建一个多并发的线程池,任务量达到最大并发数时,超出的线程进入队列等待执行。

弊端:队列长度为Integer.MAX_VALUE,当大量任务堆积在队列中,从而导致OOM。

 

1.2 newSingleThreadExecutor

基于ThreadPoolExecutor。

创建一个单线程的线程池,其中只有一个工作线程执行任务。

弊端:队列长度为Integer.MAX_VALUE,当大量任务堆积在队列中,从而导致OOM。

 

1.3 newCachedThreadPool

基于ThreadPoolExecutor。

创建一个无工作线程的线程池,来一个任务就执行一个任务。

弊端:最大线程数是Integer.MAX_VALUE,当大量任务时会创建大量线程,从而导致OOM。

 

1.4 newScheduledThreadPool

基于ThreadPoolExecutor。

创建一个可定时执行的线程池。

弊端:最大线程数是Integer.MAX_VALUE,当大量任务时会创建大量线程,从而导致OOM。

 

1.5 newWorkStealingPool

JDK1.8引入,基于ForkJoinPool。

创建一个抢占式操作的线程池,每个并行线程都有各自的队列,每个线程执行各自队列的任务。

 


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

ThreadPoolExecutor的构造函数由corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler7个参数组成。

 

2.1 corePoolSize

核心线程数。

线程池中初始的线程数,即使执行任务数小于核心线程数,线程池也不会回收这些线程。

 

2.2 maximumPoolSize

最大线程数。

该线程池最多创建多少个线程同时执行任务。

当任务数超过核心线程数(核心线程数+队列最大长度)时,线程池开始创建新线程来执行任务。

 

2.3 keepAliveTime

空闲线程保留时间。

工作线程数超过核心线程数时,多余的空闲线程会在保留时间后回收线程进行销毁。

 

2.4 unit

空闲线程保留时间的单位。

 

2.5 workQueue

任务队列。包括SynchronousQueue、ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue。

1)ArrayBlockingQueue

有界队列。该队列是基于数组的有界队列,所以在初始化的时候需要指定队列的长度。

当任务添加到线程池后,线程池会分配线程执行。当任务数超过corePoolSize后,多出来的任务添加到队列中。当多出来的任务已经塞满了队列后,将根据线程池的拒绝策略进行处理(拒绝策略后面会写)。

 

2)SynchronousQueue

同步队列。该队列我理解是长度为1的队列,put(生产与take(消费都是阻塞操作,当该任务生产后没有被消费将发生阻塞,当队列中没有可消费的任务时阻塞,这种机制也叫作配对通信机制。

同步队列使用CAS实现线程管理,而其他BlockingQueue使用AQS实现线程管理。

当任务添加到使用同步队列的线程池后,任务通过阻塞的方式进入队列,然后该任务将选择空闲线程执行,当然如果没有空闲线程则创建新线程执行。

 

3)LinkedBlockingQueue

无界队列。该队列是基于链表的无界队列。

所以使用这种队列的时候maximumPoolSize实际是无效的,因为当线程池中的工作线程达到corePoolSize后,所有新任务将会持续增加到队列中。

 

4)PriorityBlockingQueue

优先队列。该队列是基于数组的无界队列。

当任务添加到线程池后,线程池会分配线程执行。当任务数超过corePoolSize后,多出来的任务在添加到队列后会根据任务的优先级重新排列,优先级高的排在队列前面。换句话说由于优先级不同,会出现“后进先出”的情况。

由于优先队列也属于无界队列,所以使用这种队列的时候maximumPoolSize实际是无效的,道理和LinkedBlockingQueue是一样的。

 

5)LinkedBlockingDeque

双端无界队列。该队列是基于双向链表的无界队列,由于基于双向队列所以可以从队列的两端插入和移除元素,支持FIFO和FILO。

 

6)DelayQueue

延迟队列。该队列是基于数组的无界队列,由于该队列在PriorityQueue基础上实现的,先按延迟优先级排序。 

当任务添加到线程池后,线程池会分配线程执行。当任务数超过corePoolSize后,多出来的任务在添加到队列后会根据任务的延迟优先级重新排列。延迟时间短的排在队列前面。换句话说由于优先级不同,会出现“后进先出”的情况。

 

7)LinkedTransferQueue

//todo: 待学习补充

 

2.6 threadFactory

线程工厂。

用于创建线程,一般使用Executors.defaultThreadFactory()创建。

 

2.7 handler

拒绝策略。包括AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy。

当任务数超过(最大线程数+队列最大长度)时,如何处理多出来的任务。

1)AbortPolicy

该策略拒绝多出来的任务添加到线程池中,直接抛异常(RejectedExecutionException)。

 

2)CallerRunsPolicy

该策略将多出来的任务放到调用添加线程池的线程中执行。

 

3)DiscardPolicy

该策略将丢弃多出来的任务。

 

4)DiscardOldestPolicy

该策略将丢弃线程池中最早的任务(处于队头的任务),然后将多出来的任务添加到线程池中

 


三、创建ThreadPoolExecutor线程池

线程池的各项参数要根据实际的业务进行配置,下面是一个例子。

public class Test{
    private ThreadPoolExecutor executor;

    @PostConstruct
    private void initExecutor(){
        if(null == executor){
            executor = new ThreadPoolExecutor(
                    1,
                    10,
                    15,
                    TimeUnit.SECONDS,
                    new ArrayBlockingQueue<>(15),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy()
            );
        }
    }
}

 


四、通过ThreadPoolExecutor创建线程

4.1 创建单个不等待执行完成的异步线程

由于示例的线程池使用了AbortPolicy策略,所以runTask方法不会被阻塞。

public class Test{
    private ThreadPoolExecutor executor;

    @PostConstruct
    private void initExecutor(){
        if(null == executor){
            executor = new ThreadPoolExecutor(
                    1,
                    10,
                    15,
                    TimeUnit.SECONDS,
                    new ArrayBlockingQueue<>(15),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy()
            );
        }
    }

    public void runTask(){
        CompletableFuture.runAsync(() ->{
            // todo: 业务代码
        }, executor);
    }
}

 

4.2 创建单个等待执行完成的异步线程

在CompletableFuture.runAsync后加上join,只要没有触发AbortPolicy策略,runTask方法在执行后会被线程阻塞,直到线程结束。

public class Test{
    private ThreadPoolExecutor executor;

    @PostConstruct
    private void initExecutor(){
        if(null == executor){
            executor = new ThreadPoolExecutor(
                    1,
                    10,
                    15,
                    TimeUnit.SECONDS,
                    new ArrayBlockingQueue<>(15),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy()
            );
        }
    }

    public void runTask(){
        CompletableFuture.runAsync(() ->{
            // todo: 业务代码
        }, executor).join();
    }
}

 

4.3 创建多个不等待执行完成的异步线程

在项目中,经常需要创建多个线程业务,且希望这些业务不会阻塞外部方法,通过CompletableFuture.allOf可以创建多个线程。

例如针对每个order,我都需要执行各自的业务,但是希望线程任务不阻塞runTask方法。

public class Test{
    private ThreadPoolExecutor executor;

    @PostConstruct
    private void initExecutor(){
        if(null == executor){
            executor = new ThreadPoolExecutor(
                    1,
                    10,
                    15,
                    TimeUnit.SECONDS,
                    new ArrayBlockingQueue<>(15),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy()
            );
        }
    }

    public void runTask(){
        List<Order> orders = new ArrayList<>();

        CompletableFuture.allOf(orders.stream().map(t ->
                CompletableFuture.runAsync(() ->{
                    // todo:业务代码
                }, executor)
        ).toArray(CompletableFuture[]::new));
    }
}

 

4.4 创建多个等待执行完成的异步线程

在项目中,经常需要先完成多个异步线程后,再进行后续的操作,只需要在CompletableFuture.allOf后执行join就可以实现。

例如针对每个order,我都需要执行各自的业务,但是希望线程任务都完成后再继续执行runTask方法。

public class Test{
    private ThreadPoolExecutor executor;

    @PostConstruct
    private void initExecutor(){
        if(null == executor){
            executor = new ThreadPoolExecutor(
                    1,
                    10,
                    15,
                    TimeUnit.SECONDS,
                    new ArrayBlockingQueue<>(15),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy()
            );
        }
    }

    public void runTask(){
        List<Order> orders = new ArrayList<>();

        CompletableFuture.allOf(orders.stream().map(t ->
                CompletableFuture.runAsync(() ->{
                    // todo:业务代码
                }, executor)
        ).toArray(CompletableFuture[]::new)).join();
    }
}

 

posted @ 2022-06-24 08:55  粽先生  阅读(504)  评论(0编辑  收藏  举报