Executors 和 ThreadPoolExecutor 和 CompletableFuture 三者 区别

一、Executors 和 ThreadPoolExecutor 的区别在于:

Executors是一个线程池的工具类,而ThreadPoolExecutor是Executor接口的一个实现,是线程池的核心类。‌ Executors提供了多种快速创建线程池的方法,而ThreadPoolExecutor则提供了更高的自定义和控制能力‌。

Executors是一个工具类,用于快速创建不同类型的线程池,如固定大小的线程池、可缓存的线程池和单线程池等。它通过构造ThreadPoolExecutor的不同参数实例来创建线程池,适用于不想深入线程池内部实现的场景。而ThreadPoolExecutor是Java并发包中提供的具体实现类,继承自Executor接口,提供了更细致的控制和配置选项,适用于需要高度自定义线程池行为的场景‌。

使用Executors创建线程池的优点是简单快捷,通过调用Executors提供的静态方法即可快速创建一个线程池,无需关心线程池的内部实现细节。缺点是缺乏灵活性,无法根据具体需求调整线程池的参数。而使用ThreadPoolExecutor创建线程池的优点是高度自定义,可以根据具体需求设置核心参数,如核心线程数、最大线程数、存活时间和工作队列等。缺点是需要更多的代码来实现,对于简单的需求来说可能过于复杂‌

二、Excutors 与 ThreadPoolExcutor 的关系结论:

线程池的创建分为两种:

  • Executors
  • ThreadPoolExecutor

1.0、Executors

Executors 创建线程池的方式有以下几种:

  • Executors.newFixedThreadPool:创建固定大小的线程池
  • Executors.newWorkStealingPool:创建抢占式线程池
  • Executors.newSingleThreadExecutor:创建单个线程的线程池
  • Executors.newCachedThreadPool:创建可缓存的线程池
  • Executors.newSingleThreadScheduledExecutor:创建单线程可执行延迟任务的线程池
  • Executros.newScheduledThreadPool:创建可执行延迟任务的线程池

public class Executors {
    /**
     * 创建可重用的固定数量的线程池。如果所有线程都处在活动状态,提交额外任务
     * 的时候,超出的线程在队列中等待。队列类型是 LinkedBlockingQueue
     * nThreads 为创建线程池的数量
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

    /**
     * Java 8 新增的线程池,具有抢占式操作的线程池,每个线程都有一个任务队列存放任务,
     * 当线程发现自己的队列没有任务了,也就是先工作完了的线程就去帮助没处理完的线程工作。
     * 以实现最快完成工作。
     * 是基于 ForkJoinPool 创建的线程池,parallelism 参数自定义并行度。
     */
    public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

    /**
     * 同上,但是并行度是根据获取当前系统的 CPU 核心数来判断。
     */
    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

    /**
     * 创建固定数量线程的线程池,可控制线程最大并发数,超出的线程会在队列中等待。
     * 多了一个 ThreadFactory 参数,线程工厂,主要用来创建线程池
     */
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

    /**
     * 创建单个线程的线程池。它可以保证先进先出的执行顺序。
     */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

    /**
     * 同上。
     */
    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }

    /**
     * 创建一个可缓存的线程池。适用于短期执行的异步任务,从代码中可以看出线程数的设定是
     * Integer.MAX_VALUE,不对线程池的大小做设定,可以创建无限数量的线程。
     * 但也就不适合执行长时间运行的任务,可能会导致系统资源耗尽。
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

    /**
     * 同上
     */
    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

    /**
     * 创建一个单线程的可以执行延迟任务的线程池,可以安排以给定延迟后执行的命令或定时执行的命令。
     * 
     */
    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }

    /**
     * 同上
     */
    public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1, threadFactory));
    }

    /**
     * 创建一个可以执行延迟任务的线程池,任务数量自定义
     */
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

    /**
     * 同上
     */
    public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }
}
 

1.1、ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize, // 核心线程数,线程池中始终存活的线程数
                           // 最大线程数,线程池中允许的最大线程数
                          int maximumPoolSize,
                          // 最大线程数可以存活的时间
                          long keepAliveTime, 
                          // 时间单位
                          TimeUnit unit, 
                          // 阻塞队列,workQueue 包含七种
                          BlockingQueue<Runnable> workQueue, 
                           // 线程工厂,主要用来创建线程
                          ThreadFactory threadFactory,
                          // 拒绝策略
                          RejectedExecutionHandler handler ) {
    super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    this.prestartAllCoreThreads();
}

三、ThreadPoolExecutor的重要参数

1、corePoolSize:核心线程数

1     * 核心线程会一直存活,及时没有任务需要执行
2     * 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
3     * 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭

2、maxPoolSize:最大线程数

1     * 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
2     * 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常

3、 keepAliveTime:线程空闲时间

1     * 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
2     * 如果allowCoreThreadTimeout=true,则会直到线程数量=0

4、allowCoreThreadTimeout:允许核心线程超时

5、queueCapacity:任务队列容量(阻塞队列)

    * 当核心线程数达到最大时,新任务会放在队列中排队等待执行

6、rejectedExecutionHandler:任务拒绝处理器

 1     * 两种情况会拒绝处理任务:
 2         - 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
 3         - 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
 4     * 线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
 5     * ThreadPoolExecutor类有几个内部实现类来处理这类情况:
 6         - AbortPolicy 丢弃任务,抛运行时异常
 7         - CallerRunsPolicy 执行任务
 8         - DiscardPolicy 忽视,什么都不会发生
 9         - DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
10     * 实现RejectedExecutionHandler接口,可自定义处理器
  • 拒绝策略

rejectedExectutionHandler参数字段用于配置绝策略,常用拒绝策略如下

AbortPolicy:用于被拒绝任务的处理程序,它将抛出RejectedExecutionException

CallerRunsPolicy:用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。

DiscardOldestPolicy:用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。

DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。

四、ThreadPoolExecutor执行顺序

线程池按以下行为执行任务

1 1. 当线程数小于核心线程数时,创建线程。
2 2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3 3. 当线程数大于等于核心线程数,且任务队列已满
4     - 若线程数小于最大线程数,创建线程
5     - 若线程数等于最大线程数,抛出异常,拒绝任务

五、如何设置参数

计算公式分为:

CPU密集型(CPU-bound)

CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading 很高。
在多重程序系统中,大部分时间用来做计算、逻辑判断等CPU动作的程序称之CPU bound。例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部分时间用在三角函数和开根号的计算,便是属于CPU bound的程序。
CPU bound的程序一般而言CPU占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现因此屏蔽掉了等待I/O的时间。
线程数一般设置为:
线程数 = CPU核数+1 (现代CPU支持超线程)

IO密集型(I/O bound)

IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。
I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力
线程数一般设置为:
线程数 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

1、默认值

  • corePoolSize=1
  • maxPoolSize=Integer.MAX_VALUE
  • keepAliveTime=60s
  • allowCoreThreadTimeout=false
  • queueCapacity=Integer.MAX_VALUE
  • rejectedExecutionHandler=AbortPolicy()
  • 需要根据几个值来决定
    •   tasks :每秒的任务数
    •   tasktime:每个任务花费时间
    •   responsetime:系统允许容忍的最大响应时间,比如每个任务的响应时间不得超过2秒。

2、默认值
corePoolSize:
每个任务需要tasktime秒处理,则每个线程每钞可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime),即taskstasktime个线程数。假设系统每秒任务数为100 -1000,每个任务耗时0.1秒,则需要1000.1至1000*0.1,即10~100个线程。那么corePoolSize应该设置为大于10,具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%的情况下第秒任务数小于200,最多时为1000,则corePoolSize可设置为20。

queueCapacity:
任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(corePoolSize/tasktime)*responsetime: (20/0.1)*2=400,即队列长度可设置为400。
队列长度设置过大,会导致任务响应时间过长,切忌以下写法:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也将随之陡增。

关于网上说这个公式有问题,应该是(corePoolSize/tasktime)*responsetime-corePoolSize。400-20=380,不然队列的最后20个会超时。(PS:还没有测试过)

maxPoolSize:
当系统负载达到最大值时,核心线程数已无法按时处理完所有任务,这时就需要增加线程。每秒200个任务需要20个线程,那么当每秒达到1000个任务时,则需要(1000-queueCapacity)*(20/200),即60个线程,可将maxPoolSize设置为60。

keepAliveTime:
线程数量只增加不减少也不行。当负载降低时,可减少线程数量,如果一个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程。

allowCoreThreadTimeout:
默认情况下核心线程不会退出,可通过将该参数设置为true,让核心线程也退出。

以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,比如,当线程数量达到50时,CPU达到100%,则将maxPoolSize设置为60也不合适,此时若系统负载长时间维持在每秒1000个任务,则超出线程池处理能力,应设法降低每个任务的处理时间(tasktime)。    

总结基于实践有以下几个配置公式:
1、corePoolSize = 每秒需要多少个线程处理=tasks/(1/tasktime) =tasks*tasktime
2、queueCapacity = (coreSizePool/tasktime)*responsetime
3、maxPoolSize = (max(tasks)- queueCapacity)/(1/tasktime)

附:
关于queueCapacity任务队列java中提供了四种方式:直接提交队列、有界任务队列、无界任务队列、优先任务队列
1.SynchronousQueue
使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略;

2.ArrayBlockingQueue
使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。

3.LinkedBlockingQueue
使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。

4.PriorityBlockingQueue
PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。

————————————————

————————————————

————————————————

六、CompletableFuture 和 ThreadPoolExecutor 区别


CompletableFuture 和ThreadPoolExecutor 都是异步处理,但是在使用场景上还是有很大差异的,CompletableFuture 是偏异步处理操作,ThreadPoolExecutor 偏处理计算和大量请求的场景
 
1、功能和用途:

1)、CompletableFuture:CompletableFuture是用于异步编程的工具,它提供了一种方便的方式来处理异步任务的结果。它支持链式操作,可以将多个异步任务组合和协调起来,实现更复杂的异步处理逻辑。CompletableFuture适用于需要对多个异步任务进行组合和协调的场景。

ThreadPoolExecutor:ThreadPoolExecutor是Java中的一个线程池实现,用于管理和调度多个线程执行任务。它可以控制并发线程的数量,避免过多的线程创建和销毁开销,提高任务的执行效率。ThreadPoolExecutor适用于处理大量的计算密集型任务或IO密集型任务,通过线程池来管理和复用线程,提高系统的并发处理能力。

编程模型:

2)、CompletableFuture:CompletableFuture是基于Future和Promise模型的扩展,它提供了更丰富的异步编程功能。它允许使用回调函数、函数式操作和异常处理等高级编程模式,可以更方便地处理异步任务的结果和异常情况。

ThreadPoolExecutor:ThreadPoolExecutor是一个底层的线程池实现,它主要关注线程的管理和任务的调度执行。它并没有提供特定的编程模型,需要手动管理任务的提交和线程的调度。

灵活性:

3)、CompletableFuture:CompletableFuture提供了更灵活的方式来处理异步任务。它可以通过各种方法和组合操作来创建和组织异步任务,支持串行、并行、汇聚等不同的操作模式。

ThreadPoolExecutor:ThreadPoolExecutor相对而言更为底层,需要手动管理任务的提交和线程的调度。它可以手动设置线程池的核心线程数、最大线程数、任务队列等参数,提供更精细的控制和调整。

4)、综上所述,CompletableFuture适用于更复杂的异步处理逻辑,支持高级编程模式和操作,而ThreadPoolExecutor适用于更基本的多线程任务管理和调度。根据具体的需求和场景,选择合适的工具来实现异步编程和处理多线程任务。在一些情况下,CompletableFuture和ThreadPoolExecutor可以结合使用,以充分发挥它们各自的优势。

2、什么是 CompletableFuture

在Java中CompletableFuture用于异步编程,异步通常意味着非阻塞,运行任务单独的线程,与主线程隔离。并且通过回调可以在主线程中得到异步任务的执行状态,是否完成和异常等信息。

通过这种方式,主线程不会被阻塞,不需要一直等到子线程完成。主线程可以并行的执行其他任务。使用这种并行方式,可以极大的提高程序的性能。

3、应用场景

(1)、描述依赖关系

thenApply() 把前面异步任务的结果,交给后面的Function

thenCompose()用来连接两个有依赖关系的任务,结果由第二个任务返回

(2)、描述and聚合关系

thenCombine 任务合并,有返回值

thenAccepetBoth 两个任务执行完成后,将结果交给thenAccepetBoth消耗,无返回值

runAfterBoth 两个任务都执行完成后,执行下一步操作(Runnable)

(3)、描述or聚合关系

applyToEither 两个任务谁执行的快,就使用那一个结果,有返回值

acceptEither 两个任务谁执行的快,就消耗那一个结果,无返回值

runAfterEither 任意一个任务执行完成,进行下一步操作(Runnable)

(4)、并行执行

CompletableFuture类自己也提供了anyOf()和allOf()用于支持多个CompletableFuture并行执行

(5)、静态方法创建
/**
supply 和 run 的主要区别就是 supply 可以有返回值,run 没有返回值。
Executor 就是用来执行异步任务的线程池,
如果不传Executor 的话,默认是ForkJoinPool这个线程池的实现。
一旦通过静态方法来构造,会立马开启异步线程执行Supplier或者Runnable提交的任务。
**/
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);

public static CompletableFuture<Void> runAsync(Runnable runnable);
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);

(6)、获取任务执行结果

public T get();

public T get(long timeout, TimeUnit unit);

public T getNow(T valueIfAbsent);

public T join();

get()和get(long timeout, TimeUnit unit)是实现了Future接口的功能,两者主要区别就是get()会一直阻塞直到获取到结果;

get(long timeout, TimeUnit unit)值可以指定超时时间,当到了指定的时间还未获取到任务,就会抛出TimeoutException异常。

getNow(T valueIfAbsent):就是获取任务的执行结果,但不会产生阻塞。如果任务还没执行完成,那么就会返回你传入的valueIfAbsent 参数值,如果执行完成了,就会返回任务执行的结果。

join():跟get()的主要区别就是,get()会抛出检查时异常,join()不会。
 

————————————————————————————————————

————————————————————————————————————

————————————————————————————————————

示例:

import com.alibaba.fastjson2.JSON;


import com.test.DingDingSendMessage;
import com.test.WaybillWarnMsgQueue;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.concurrent.*;

@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class MsgqueueTest {


    @Test
    public void waybillTest() {

        /**
         * newSingleThreadScheduledExecutor()
         * newSingleThreadScheduledExecutor 是 Java 中 java.util.concurrent 包的 Executors 类的一个静态工厂方法。
         * 这个方法用于创建一个单线程的调度执行器,它可以安排命令在给定的延迟后运行,或者定期地执行。
         * 以下是 newSingleThreadScheduledExecutor 方法的实现原理、源代码分析以及实现过程:
         * 实现原理
         * 单线程执行: 执行器确保所有任务都在单个线程中顺序执行,这保证了任务的执行顺序。
         * 定时任务: 支持延迟执行和周期性执行任务。
         * 任务队列: 所有任务首先被放入一个任务队列中,然后由单线程按顺序执行。
         */
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        try {
            // for (int i = 1; i < 1220; i++) {
            for (int i = 1; i < 100; i++) {
                DingDingSendMessage message = new DingDingSendMessage();
                message.setText(String.format("[第 %s 条] : %s", i, "添加任务测试..."));
                Boolean aBoolean = WaybillWarnMsgQueue.addMessage(message);
                log.info(String.format("[第 %s 条] : 结果 --- %s", i, aBoolean));
            }

            /**
             * 方法实现周期性执行
             * command:执行线程
             * initialDelay:初始化延时即首次执行的延时时间
             * period:两次开始执行最小间隔时间即定时执行的间隔时间
             * unit:计时单位
             */
            executor.scheduleAtFixedRate(
                    () -> {
                        int size = WaybillWarnMsgQueue.getSize();
                        log.info("当前任务 size:{}", size);
                        for (int i = 1; i <= 20; i++) {
                            DingDingSendMessage message = WaybillWarnMsgQueue.getMessage();

                            // 处理自定义业务...
                            log.info("message : {}", JSON.toJSONString(message));
                        }
                        int size2 = WaybillWarnMsgQueue.getSize();
                        log.info("剩余任务 size:{}", size2);

                        if (WaybillWarnMsgQueue.getSize() <= 0) {
                            log.info("executor.shutdown [线程关闭,资源释放] WaybillWarnMsgQueue.getSize : {}", WaybillWarnMsgQueue.getSize());
                            // 线程关闭,资源被及时释放
                            executor.shutdown();
                        }

                    }, 0, 5, TimeUnit.SECONDS
            );


        } catch (Exception e) {
            log.error("waybillTest.getMessage : {}", e.getMessage(), e);
        }

    }
    

import java.util.concurrent.LinkedBlockingQueue;


public class WaybillWarnMsgQueue {

    /**
     * LinkedBlockingQueue是一个基于已链接节点的,范围任意的blocking queue
     * 此队列按FIFO(先进先出)排序元素
     * 新元素插入到队列的尾部,并且队列获取操作会获得位于队列头部的元素
     * 链接队列的吞吐量通常要高于基于数组的对列(ArrayBlockingQueue),但是在大多数并发应用程序中,其可预知的性能要低
     * 可选的容量范围构造方法参数作为防止队列过度扩展的一种方法,如果未指定容量,则等于Integer.MAX_VALUE,除非插入节点会使队列超出容量,否则每次插入后会动态地创建链接节点
     */
    private static final LinkedBlockingQueue<DingDingSendMessage> waybillQueue = new LinkedBlockingQueue<>(1200);


    /**
     * offer(E e):将指定的元素插入此队列中(如果立即可行且不会违反容量限制),
     * 成功时返回true,如果当前没有可用的空间,则返回false。
     *
     * @param message
     * @return
     */
    public static Boolean addMessage(DingDingSendMessage message) {

        return waybillQueue.offer(message);
    }

    /**
     * poll():移除并返回此队列的头部,如果此队列为空,则返回null。
     *
     * @return
     */
    public static DingDingSendMessage getMessage() {

        return waybillQueue.poll();
    }

    /**
     * 返回队列中的元素个数。
     *
     * @return
     */
    public static int getSize() {

        return waybillQueue.size();
    }
}

import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;


@Data
public class DingDingSendMessage implements Serializable {

    private static final long serialVersionUID = -8465129015190922659L;

    /**
     * 告警电话
     */
    private String alertPhone;

    /**
     * 告警消息文本
     */
    private String text;
}

备注:

1)、几个概念区分:Executor、ExecutorService、Executors、ThreadPoolExecutor
这几个概念很容易弄混,这里简单从概念层面区分一下。

一句话概括:

Executor:任务(Runnable)执行器,调用者只需要提交任务,而无需关心任务执行细节。

ExecutorService:扩展了Executor接口,增加了诸如任务生命周期管理、执行有返回值的任务(Callable)、批量执行任务等方法。

Executors:提供了创建Executor、ExecutorService、ThreadFactory、Callable等的工厂方法和一些其他工具方法。

ThreadPoolExecutor:ExecutorService的具体线程池实现。

2)、线程池

为什么使用线程池?
频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。
对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。
系统无法合理管理内部的资源分布,会降低系统的稳定性。

使用线程池的好处?
重用存在的线程,减少对象创建、消亡的开销。
有效的控制最大并发数,提高系统资源使用率。
统一的分配、调优和监控、可定时执行、定期执行。

线程池所在包java.util.concurrent
顶级接口Executor,真正的线程池接口是ExecutorService

3)、Executors和ThreaPoolExecutor创建线程池的区别

在 Java 中,确实存在着 Executors 工厂类的几种创建线程池的方法存在一些缺陷,让我们来具体分析一下。

1、newFixedThreadPool 和 newSingleThreadExecutor

这两种方法都使用了无界的 LinkedBlockingQueue 作为工作队列,在任务提交速度持续大于任务处理速度的情况下,可能会导致队列中累积大量的任务,从而消耗大量内存。如果任务量非常大或者任务执行时间非常长,那么这种设计就会面临 OutOfMemoryError (简称:OOM)的风险。

2、newCachedThreadPool 和 newScheduledThreadPool

这两种方法在某些情况下可能会创建大量的线程,因为它们的最大线程数是 Integer.MAX_VALUE。如果系统负载突然增加,那么就可能创建非常多的线程,从而消耗大量系统资源,甚至导致 OutOfMemoryError(简称:OOM)。

而相较之下,使用 ThreadPoolExecutor 构造函数创建线程池可以更好地控制线程池的行为,避免上述问题。通过 ThreadPoolExecutor 的构造函数,可以显式地指定核心线程数、最大线程数、工作队列类型、拒绝策略等参数,从而更精细地控制线程池的行为。

因此,在开发中,建议根据具体需求和系统负载情况,选择合适的线程池创建方法,并结合 ThreadPoolExecutor 来进行更为细致的参数配置,以避免潜在的问题。

SingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。

FixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。

CachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器。

ScheduledThreadPoolExecutor 用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景。

3、ScheduledExecutorService

ScheduledExecutorService是 jdk1.5中引入了并发包,目的是为了弥补Timer的缺陷, ScheduledExecutorService内部为线程池。

是Java中用于调度任务的接口。具体来说,它是ExecutorService的子接口,扩展了线程池的功能,允许在预定的时间执行任务,也可以周期性地重复执行任务;

参考原文链接:深入剖析Java线程池的核心概念与源码解析:从Executors、Executor、execute逐一揭秘_java executors-优快云博客

Java Executors类的9种创建线程池的方法及应用场景分析-优快云博客

CompletableFuture详细讲解-优快云博客

CompletableFuture使用(全网最详细!!!)_completablefuture用法-优快云博客

Java——基于CompletableFuture的流水线并行处理_completefuture并行处理-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值