Executors 和 ThreadPoolExecutor 和 CompletableFuture 三者 区别

Java ThreadPoolExecutor核心参数详解与生产级配置 ThreadPoolExecutor是Java并发编程中实现异步任务调度的核心组件,其本质是一套基于线程生命周期、任务队列策略与拒绝机制的动态资源调控系统。理解corePoolSize与maximumPoolSize的协同关系、keepAliveTime对临时线程的实际作用范围、workQueue(如ArrayBlockingQueue/SynchronousQueue)对吞吐与稳定性的影响,以及handler(如CallerRunsPolicy)在流量洪峰下的自我保护价值,直接决定服务的SLA保障能力。这 阅读详情
AI摘要

本文深入探讨Java线程池,对比Executors和ThreadPoolExecutor的区别与联系。介绍ThreadPoolExecutor重要参数、执行顺序及参数设置方法,还分析了CompletableFuture和ThreadPoolExecutor在功能、编程模型、灵活性上的差异及应用场景,为Java开发中线程池使用提供参考。

一、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并行处理-优快云博客

为什么阿里不建议用excutors创建线程池 1 前言: 大家都知道,阿里规范中有一条是不允许用excutors去创建线程池,而是采用ThreadPoolExecutor的原生方式去创建。很早就听过所过这种说法,但是一直都没去搞清楚是为什么,今天就查阅资料去了解了这个问题。 2 Excutors创建线程的方式 通过Excutors来创建线程池,有4种创建线程的方法。 newCachedThreadPool创建一个可缓存线程池,如果线程池的大小超过了处理任务所需的线程,那么就会回收部份空闲线(60秒不处理任务)线程... 阅读详情

相关推荐

线程池——Executor、Executors、ExecutorService、ThreadPoolExecutor、ThreadPoolTaskExecutor之间的区别

java.util.concurrent.Executor 负责线程的使用和调度的根接口|–ExecutorService 子接口: 线程池的主要接口|–ThreadPoolExecutor 线程池的实现类|–ScheduledExceutorService 子接口: 负责线程的调度|–ScheduledThreadPoolExecutor : 继承ThreadPoolExecutor,实现了ScheduledExecutorService。

qq_36042938的博客 1880

Executors一篇就够

Executors框架包含的内容十分的多:看图: Executors框架前言一、pandas是什么?二、使用步骤1.引入库2.读入数据总结 前言 提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。 提示:以下是本篇文章正文内容,下面案例可供参考 一、pandas是什么? 示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。 二、使用步骤 1.引入库 代码

qq_44828283的博客 4889

关于常用线程池CompletableFuture和Future的介绍

Java异步编程中,Future和CompletableFuture是两种主要实现方式。Future通过ExecutorService.submit()提交任务,需调用get()阻塞获取结果,适合简单异步场景;而CompletableFuture采用链式非阻塞回调机制,支持任务编排和异常处理,默认使用ForkJoinPool,更适合复杂异步流程。核心区别在于:Future需主动获取结果(同步等待),CompletableFuture可通过回调自动处理(异步响应)。新项目更倾向使用CompletableFut

weixin_50055999的博客 1044

多线程基础(九)Executors线程池

Executors线程池

m0_47743175的博客 2185

Spring Boot项目里CompletableFuture抛CompletionException?这份避坑指南和调试技巧请收好

本文深入解析Spring Boot项目中CompletableFuture抛出CompletionException的常见问题,提供详细的避坑指南和调试技巧。通过剖析异常传播机制、线程池配置优化及防御式编程实践,帮助开发者有效解决异步任务中的异常处理难题,提升系统稳定性。

dienangpiao2051的博客 555

Java线程池与ForkJoinPool核心区别与实战指南

线程池是Java并发编程的核心组件,通过复用线程资源提升系统性能。ThreadPoolExecutor采用生产者-消费者模型,适合处理大量独立短任务;而ForkJoinPool基于分治算法和工作窃取机制,专为可分解的递归任务优化。在计算密集型场景如大数据处理时,ForkJoinPool能充分利用CPU多核特性,相比传统线程池有2-3倍性能提升。理解两者的核心参数如corePoolSize、workQueue和工作窃取算法,能帮助开发者根据任务特性选择最优方案。典型应用场景包括Web服务器请求处理(Threa

congbao6525的博客 479

java 线程创建Executors 和 ThreadPoolExecutor 和 CompletableFuture 三者 区别

使用Executors创建线程池的优点是简单快捷,通过调用Executors提供的静态方法即可快速创建一个线程池,无需关心线程池的内部实现细节。Executors是一个工具类,用于快速创建不同类型的线程池,如固定大小的线程池、可缓存的线程池和单线程池等。Executors是一个线程池的工具类,而ThreadPoolExecutor是Executor接口的一个实现,是线程池的核心类。‌ Executors提供了多种快速创建线程池的方法,而ThreadPoolExecutor则提供了更高的自定义和控制能力‌。

csdndd521的博客 464

Java并发编程核心详解:线程、锁、JMM与线程池实战

在Java后端开发中,并发编程是绕不开的核心技能,也是面试与线上问题排查的高频考点。它主要解决多线程环境下的原子性、可见性与有序性三大问题,具体实现依赖线程、锁、内存模型等基础机制。synchronized与volatile分别从互斥与内存屏障角度保证线程安全,而JMM(Java内存模型)与happens-before原则则定义了变量在并发访问时的可见性规则。进一步地,JUC中的锁、CAS、并发容器以及线程池,为复杂业务场景提供了高效且可控的并发工具。理解线程池的参数配置与拒绝策略,能够有效规避资源耗尽风险

weixin_33806300的博客 168

线程池 ThreadPoolExecutor ForkJoinPool

线程池 ThreadPoolExecutor ForkJoinPool 异步线程执行实现方式 CompletableFuture

仗剑执天涯的专栏 796

线程池和线程、阻塞/非阻塞队列和公平/非公平队列、Condition和CountDownLatch、interrupt()、CompletableFuture等

四种线程池面试题

の博客 337

机器学习系列(3)_逻辑回归应用之Kaggle泰坦尼克之灾

f作者: 寒小阳 &&龙心尘 时间:2015年10月。 出处: 声明:版权所有,转载请注明出处,谢谢。手把手机器学习之逻辑回归应用——Kaggle泰坦尼克之灾1.引言先说一句,年末双十一什么的一来,真是非(mang)常(cheng)欢(gou)乐(le)!然后push自己抽出时间来写这篇blog的原因也非常简单: 写完前两篇逻辑回归的介绍和各个角度理解之后,小伙伴们纷纷表示『好像很高级的样纸,

寒小阳 43万+

【Java并发】CompletableFuture详解:常用API和底层原理

1、stage_1执行的时候,会新建一个futureA,然后执行push方法。1、stage_1执行的时候,会新建一个futureA,然后执行push方法。当任意一个上游完成时,都会尝试检查另一个是否也完成,如果都完成,则执行合并函数。3、stage_1执行完成后,会先将futureB抛出到线程池中进行执行,然后再将futureA抛出到线程池中执行。后续的依赖任务在触发时,会检查当前 Future 的 result 是否为 AltResult,如果是,则将异常传递下去,或调用异常处理方法。

xueping_wu的博客 462

基于ESP32与PCM5102的WiFi无损音频传输系统设计与实现

本文详细介绍基于ESP32与PCM5102的WiFi无损音频传输系统设计与实现。通过I2S接口直接传输PCM数据,避免SRC重采样,实现真正的HIFI级音质。涵盖硬件连接、软件架构、网络传输优化及实际音质对比,为音频爱好者提供高性价比的无损无线音频解决方案。

rnn9storyteller的博客 948

Java多线程----创建线程、线程池ExecutorService、异步编排CompletableFuture

自定义类实现Runnable接口;通过的方式创建线程// 方式1;继承Thread类// 方式2;实现Runnable接口).start();Java中线程池就是Executor或者对象实例之前通过不能重复利用线程,有多少任务就创建多少个线程如果需要处理大量任务,就需要频繁地创建和销毁线程会浪费时间和效率如果同一时刻存在大量的线程,那么线程之间还存在竞争资源,CPU上下切换等问题线程池通过预先创建一定数量的线程,让这些线程处理来自任务队列中的任务,而不是频繁创建和销毁线程。

idooy的学习笔记 1223

【CV论文精读】【协同感知综述】Collaborative Perception for Autonomous Driving :Current Status and Future Trend

感知是自动驾驶系统的关键模块之一,近年来取得了很大进展。然而,单个车辆的有限能力导致感知性能提高的瓶颈。为了突破个体感知的局限,协作感知被提出,它使车辆能够共享信息来感知视线和视野之外的环境。本文综述了有前途的协作感知技术的相关工作,包括介绍了基本概念,概括了协作模式,总结了协作感知的关键要素和应用。最后,我们讨论了这一研究领域面临的挑战和问题,并给出了一些潜在的进一步方向。

weixin_44184852的博客 4689

cut&tag学习笔记

上图是我得到的结果,下图是教程的结果,比对结果有一些差异,我也不知道具体原因是啥。谷歌了一下,发现原因可能是因为环境变量的问题,系统不知道picard的具体位置,需要设置全局环境变量,遂查了一下picard的位置。我发现我做出来的结果测序深度一致,但是比对的结果有一点差异,不知道是我的操作问题,还是参考基因组更新了导致比对结果出现了些许不同。上图是我得到的结果,下图是教程的结果,除了IgG_rep1外比对结果一致。上图是我的结果,下图是教程的结果,结果不太一致,不知道是具体的原因是什么。

weixin_52073820的博客 2422

Java并行编程实战:CompletableFuture与ForkJoinPool构建异步任务框架

并发与并行是提升程序性能的核心概念。并发通过时间片轮转实现多任务交替执行,而并行则利用多核硬件实现任务同时执行,显著提升计算密集型任务的吞吐量。在Java生态中,线程池技术是管理并行执行单元的关键,它有效避免了直接创建线程的资源消耗问题。CompletableFuture作为Java 8引入的异步编程利器,不仅增强了Future的功能,更通过组合式API支持复杂异步工作流的构建,实现了任务间的灵活串联与并联。这种技术价值在于能够将独立任务封装为异步单元,再通过allOf、anyOf等方法实现结果的聚合与协调

weixin_33894640的博客 330

【TouchDesigner】三维渲染

Cmera COMP Uniform Scale(按比例缩放):沿x,y,z三个轴等比例缩放相机(通常不建议改变相机大小)。 Constrain To(约束到):让相机附属到某个指定元件上,跟随指定元件运动。 Look At(朝向):让相机在移动过程中始终面对指定的元件。 Pah SOP(相机的路径):输入SOP元件名字,为相机指定移动路径。 Geometry Comp Geometry COMP中的参数与Camera COMP中的参数用法大部分都相似。特殊的是它的instacne页面的

wangpuqing1997的博客 5459

线程创建与线程池创建的区别

Runnable— 无返回值Callable— 有返回值、可抛异常这两种是定义“做什么”的接口,它们可以被Thread或线程池承载运行。创建线程的方式:继承Thread、实现RunnableCallableThreadFutureTask,以及本质上更推荐的线程池提交任务方式。线程池与直接创建线程的区别:线程池是线程的复用容器,管理和任务分离;直接创建是“一次性使用”。日常开发习惯:绝大多数情况用线程池,既规范又高效。创建方式优点缺点推荐场景简单快捷无界队列,任务堆积 OOM。

m0_62474094的博客 295

【verilog语法详解:从入门到精通】

Verilog语法详解:从入门到精通 摘要:本文系统讲解Verilog硬件描述语言的核心语法与应用。Verilog作为数字电路设计标准,具有并行执行、硬件映射和层次化三大特点。文章从基础语法框架入手,详细介绍了模块定义、端口类型(input/output/inout)声明规范,以及线网型(wire)和寄存器型(reg)两种核心数据类型的区别与应用场景。重点讲解了组合逻辑(assign连续赋值)和时序逻辑(always块)的实现方式,包括阻塞赋值(=)与非阻塞赋值(<=)的关键差异。此外,还涵盖了参数(

m0_58476824的博客 4680

NVIDIA A100驱动及CUDA安装指南

NVIDIA A100驱动及CUDA安装指南:详细介绍了在Ubuntu系统下安装A100显卡驱动和CUDA工具包的完整流程,包括环境准备、驱动安装、CUDA配置及性能优化建议。特别推荐猿界算力平台提供开箱即用的A100算力租赁服务,具备专业团队支持、灵活租期和实惠价格等优势。文末通过PyTorch测试验证了A100设备的可用性,展示了80GB显存的强大计算能力。

2401_89353317的博客 1510

Java线程池ThreadPoolExecutor核心机制与实战配置

线程池作为并发编程的核心组件,通过复用线程资源显著提升系统性能。其工作原理基于生产者-消费者模型,通过任务队列实现异步处理,核心参数包括线程数量、队列容量和拒绝策略等关键配置。合理使用线程池能有效降低资源消耗,特别适合处理高并发请求、异步任务等场景。ThreadPoolExecutor作为Java标准库实现,支持动态调整线程数、任务优先级等高级特性,在电商秒杀、实时计算等业务场景中表现优异。通过监控活跃线程数、队列积压等指标,结合SynchronousQueue等队列选型技巧,可以构建高可用的并发处理系统。

weixin_30378623的博客 461

基于coze studio开源框架二次定制开发教程

CozeStudio是一站式AIAgent开发工具,支持大模型接入、智能体编排、工作流开发等功能。采用Golang+React技术栈,基于微服务架构设计。项目包含后端API、领域逻辑、前端应用等模块,支持自定义任务流节点开发。开发流程涵盖前后端节点定义、实现逻辑编写等步骤,提供完整的开发规范和参考文档。相比商业版,开源版本在功能上有所精简,但仍保留核心开发能力。

coding change your life 790

Java CompletableFuture 异步编程实战:从基础原理到高并发应用

异步编程是现代高并发系统设计的核心概念,旨在通过非阻塞方式提升资源利用率和系统吞吐量。其核心原理是将耗时操作(如IO、远程调用)从主线程剥离,交由后台线程执行,待完成后通过回调或轮询机制通知主线程,从而避免线程阻塞等待。这项技术的核心价值在于能够显著优化响应时间、提升系统可伸缩性,并简化复杂并发流程的编排。在Java生态中,它广泛应用于微服务调用聚合、批量数据处理、实时计算等场景。本文聚焦于Java 8引入的CompletableFuture,深入解析其如何作为一套函数式异步编程工具箱,通过声明式API解决

weixin_33762130的博客 358

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Java深入解析篇二十五之CompletableFuture

是 JDK 8 引入的异步编程工具,实现了 和 两个接口: 1.2 核心设计思想 设计目标 实现方式 非阻塞异步 任务提交后立即返回 CF 对象,结果通过回调通知 链式编排 每个操作返回新的 CF,支持流式 API 组合能力 thenCompose/thenCombine/allOf/anyOf 异常传播 异常沿链传播,可在任意节点捕获处理 手动完成 complete()/completeExceptionally() 支持外部驱动 1.3 类继承关系 二、Fut

萧瑟余晖的博客 387

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

CompletableFuture顺序工作流异步执行与异常处理实践

在Java异步编程中,如何保证有依赖关系的多个任务按顺序执行,同时避免同步阻塞占用请求线程,是提升系统吞吐量的关键。CompletableFuture作为Java 8引入的异步编排工具,通过thenApply、thenCompose等阶段方法,能够将多个耗时步骤串联为一条有序执行链,前一步结果直接传递给下一步,既保持业务依赖,又将任务分派到线程池中。其核心价值在于把同步阻塞模型转换为回调驱动模型,有效降低线程空闲等待。当链上某任务抛出异常时,后续阶段默认短路不再执行,需要根据业务规则采用exceptiona

weixin_34189116的博客 386

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

上一篇: 美团一面:Spring Cloud 远程调用为啥要采用 HTTP,而不是 RPC?
下一篇: 微服务全做错了!谷歌提出新方法,成本直接暴降 90%,杀疯了!
Java_2017_csdn
博客等级 码龄10年 329粉丝 49原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值