V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
baolinliu442k
V2EX  ›  Java

实际项目中如何使用线程池

  •  1
     
  •   baolinliu442k · 312 天前 · 5029 次点击
    这是一个创建于 312 天前的主题,其中的信息可能已经有所发展或是发生改变。

    工作后,接触过定义线程池的情况只有全局一个线程池,由前辈设置,自己用就可以了。再就是使用 Springboot 提供的 @Async ,想知道大家一般在生产中的线程池是怎么定义的和使用的

    1. 线程池定义在哪里,是全局( xxThreadUtil )还是业务类(xxxService 的 private static)里面
    2. 单个线程池执行所有任务好像不太合适,应该不止一个线程池,多个线程池的话是按什么分类和分配线程池参数的呢
    3. 在一个项目里定义多个线程池感觉主要是为了线程不会相互依赖,性能上应该不会提升?
    4. 执行任务的方式,使用线程池的 submit 、execute ? 或者 CompleteableFeature?

    感觉多线程这一块的技术还是非常多内容,无论是思想还是实细节,我常常在想 node 真的好优雅啊,提供简单的 api 就很方便实现异步执行,其他语言里面好像要知道很多细节才可以编写多线程代码

    由于本人技术菜单一批,可能有的问题比较蠢,恳请大家不吝赐教

    43 条回复    2024-01-10 08:42:48 +08:00
    zznote
        1
    zznote  
       312 天前
    不想打字,你可以选择直接问 gpt ,它比我们大部分人说的明白
    baolinliu442k
        2
    baolinliu442k  
    OP
       312 天前
    以下是来自 chatgpt 的回答
    1. 线程池定义在哪里?
    线程池的定义通常应该放在一个全局的工具类(如 ThreadPoolUtil )或者专门的配置类中。这样做有助于集中管理和配置线程池,提高代码的可维护性。在业务类中定义线程池(例如在 xxxService 的 private static )通常不是最佳实践,因为线程池的配置和管理应该是全局的,而不仅仅局限于某个业务类。

    2. 单个线程池执行所有任务还是多个线程池?
    是否使用单个线程池还是多个线程池取决于任务的特性和要求。一般来说,使用单个线程池可能会更简单,但在某些情况下,使用多个线程池可能更合适。多个线程池可以根据任务的类型、优先级、执行时间等因素进行分类和分配,以更好地满足不同任务的需求。

    3. 多个线程池的优势和性能提升?
    定义多个线程池的主要目的不仅仅是为了避免线程相互依赖,还可以根据任务的性质进行更细粒度的控制。这样可以提高资源的利用率,更好地适应不同类型的任务。性能提升的效果取决于具体的应用场景和任务类型,可能并不总是显著的。在某些情况下,使用多个线程池可能更适合任务的分配和调度。

    4. 执行任务的方式?
    在使用线程池执行任务时,可以使用 submit 方法或 execute 方法,具体选择取决于你对任务执行结果的需求。
    w741069229
        3
    w741069229  
       312 天前 via iPhone   ❤️ 1
    谨慎用在生产
    Leviathann
        4
    Leviathann  
       312 天前
    异步和多线程本来就是两回事

    以前搞一堆线程,大部分都是因为阻塞 io
    siweipancc
        5
    siweipancc  
       311 天前 via iPhone
    node 并发叫优雅?你那叫异步回调,最常用的 debounce 底层还是必须有个有个异步池支持,玩过 rxjs 没?别说 Promise 一把梭╮( ̄▽ ̄"")╭
    chendy
        7
    chendy  
       311 天前
    按需,慎用,不到万不得已不给系统加这方面的复杂度
    blankmiss
        8
    blankmiss  
       311 天前
    @baolinliu442k V2EX 不允许在回复出现 gpt 回复内容
    joyhub2140
        9
    joyhub2140  
       311 天前
    业务开发就别纠结了,改用什么就用什么。

    线程池用的最多的是后端框架和客户端请求池,你没看错客户端也用的很多,而且频繁程度很高,http 异步也需要一个线程池管理着。
    qhkobold
        10
    qhkobold  
       311 天前
    用啥线程池呢,直接上 jdk21 用虚拟线程
    mmdsun
        11
    mmdsun  
       311 天前
    线程池 Spring 配置 bean 用的时候注入 + CompletableFuture
    Seulgi
        12
    Seulgi  
       311 天前   ❤️ 1
    全局,不管 util 类还是 spring bean 管理,都是其中一种实现方式。按需,小组自己评估哪些任务可以丢到哪些池里去,相当于给池定义他的领域,属于他领域的就用他,谁用了池,哪里用了池,要评估,不要一个人随便写个池,别人也随便在随便用你的池。慎用,除非没有其他办法,一般不用池。现在这个时间,一般公司用的都是 future 。Java21 的虚拟线程,现在可以忽略。生产项目都还没几个 Java21 实践。
    kuituosi
        13
    kuituosi  
       311 天前   ❤️ 1
    1.定义在合适的地方,要看具体的需求
    2.同一类的任务放一个线程池,防止互相干扰,也便于优化
    3.减少依赖和干扰,优化执行效率肯定提升性能
    4.肯定看情况使用,CompletableFuture 最强大,不仅获取异步结果,而且可以设置超时和组合
    java 的多线程已经提供足够好的库已经足够好用了,能够跟 java 多线程比的也就是协程了
    node 这种半残语言只有一个线程,根本没有多线程。只适合 io 型工作,遇到 cpu 型就是废物。
    不要提可以多进程 node 也能做 cpu 型工作,不仅麻烦不好维护,也难于优化
    而 java 的多线程有优秀的框架,使用也比较简单,工程师更多时候花在优化参数上,而且可以胜任复杂的业务场景
    node 跟 java 比完全没有一战之力
    CodeCodeStudy
        14
    CodeCodeStudy  
       311 天前
    底层类库用的,业务中谨慎使用
    oneronan
        15
    oneronan  
       311 天前
    1. @Async 有坑,自定义线程池做异步任务。
    2. 统一定义线程池工具类,统一构造器,统一优雅关闭,业务需要线程池使用构造器创建,具体的线程数量根据 io 、cpu 实际情况设定。
    3. spring 的 scheduled 有坑,默认线程池核心数是 1 ,如果有很多任务,任务同间隔时间执行,会出现任务不执行的问题。解决问题:1. org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration#taskSchedulerBuilder 构建任务调度线程池,org.springframework.boot.autoconfigure.task.TaskSchedulingProperties 查看这个类,根据 @ConfigurationProperties("spring.task.scheduling")在配置文件中修改线程池相关初始化参数。2. 自定义任务调度线程池,使用 org.springframework.scheduling.config.ScheduledTaskRegistrar#setScheduler 注册。
    Aresxue
        16
    Aresxue  
       311 天前
    1.视业务而定,如果是一个低频的业务和其它业务共享一个线程池也无伤大雅,如果相对并发较高,最好是指定用自己的线程池而不是公用的线程池,@Async 也是可以指定线程池的,和 private static 的方式基本上是等价的,大多数情况下都可以用它;
    2.参数没有标准,完完全全根据业务的情况而定,这个情况不仅是当下还有对未来的适当评估;
    3.线程池主要是用于隔离线程资源和多参数任务并行降低 rt ,其对于整个应用资源的利用率并不会有显著的提升;
    4. execute 适用于没有返回值的任务,submit 的返回值是 Future ,基本上能用 submit 没啥必要了,CompleteableFuture 本来就是为了加强 Future 的。

    对于业务中我是建议能不能则不用,作为排名靠后的一个选择,顺便贴一些使用线程池的注意点:
    - 合适的任务队列及其大小,过大会造成 oom ;
    - ThreadLocal(登录信息上下文或其它的业务信息)丢失;
    - 全链路 id 丢失;
    - 合适的线程池策略和线程数(固定数目和不定数目);
    - 任务重启丢失(优雅退出);
    nothingistrue
        17
    nothingistrue  
       311 天前
    正确的讲,负责异步的是执行器,不是线程池。线程池只是执行器的组件,在此之外的组件还有任务队列、以及执行器的总控制。另外,不是所有执行器都需要线程池,你要高兴,完全可以用单线程搞个执行器。

    执行器如何配置参数,直接看各 Excutor 类的 Javadoc 即可,压根不需要求别人。执行器在何时初始化、何时销毁、以及如何获取,这是个问题,但这个问题其实不是执行其的问题,而是如何将执行器放到 JVM 的问题。这个东西 Java 用熟了自然就回了,最简单的就是直接挂 static + 使用 static 代码块。

    异步执行的原理就是这么复杂,你所谓的优雅,不是优雅,只是隐藏了底层 API ,同时也失去了定制能力的傻瓜式 API 而已。
    imokkkk
        18
    imokkkk  
       311 天前
    1.Bean 的方式创建、管理线程池

    2.不同的业务使用不同的线程池

    3.CompletableFuture 用起来很方便

    目前我是这样用 没出过啥问题

    @Configuration
    public class ThreadPoolConfig {

    @Bean(destroyMethod = "shutdown", name = "xxxxxThreadPool")
    public ThreadPoolExecutor xxxxxThreadPool() {
    ThreadFactory tf =
    new ThreadFactoryBuilder().setNameFormat("xxxxxThreadPool-%d").build();
    return new ThreadPoolExecutor(
    Runtime.getRuntime().availableProcessors() ,
    Runtime.getRuntime().availableProcessors() * 2,
    xxx,
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(xxxx),
    tf,
    new ThreadPoolExecutor.CallerRunsPolicy());
    }

    @Bean(destroyMethod = "shutdown", name = "xxxxxThreadPool")
    public ThreadPoolExecutor xxxxxThreadPool() {}

    }
    northernsongy2
        19
    northernsongy2  
       311 天前
    注意区分业务场景,举个例子,你就明白了,有 2 个服务,A 调用 B ,B 服务有几次 mysql 超时,A 调用 B 的线程池,因为 B 的超时,全部等待,然后 A---挂了。(事后 B 还嘲讽了 A 的负责人,然后群里吵起来了....) 这个应该算生产比较容易出故障的场景
    matepi
        20
    matepi  
       311 天前
    @northernsongy2 没有 SLA 就是这样的啦。其实不给 SLA ,A 也有个办法就是坏一个丢一个(池扩张一),反正 B 的问题。
    GloryJie
        21
    GloryJie  
       311 天前
    在 BFF 层聚合数据的时候用的多,一般会同时调好几个接口。这边使用上线程池一般都托管给 Spring 。
    最近在改造成基于 Dag 来编排任务执行了,不过基础还是让线程池执行
    TuringHero
        22
    TuringHero  
       311 天前
    spring:
    threads:
    virtual:
    enabled: true
    hdfg159
        23
    hdfg159  
       311 天前
    Spring Boot 3.2 打开虚拟线程开关,你都不用管了,直接 @Async 注解无脑用了
    baolinliu442k
        24
    baolinliu442k  
    OP
       311 天前
    @siweipancc 没有,觉得 await 和 async 关键字挺好的
    baolinliu442k
        25
    baolinliu442k  
    OP
       311 天前
    @ffw5b7 这篇之前也看过
    baolinliu442k
        26
    baolinliu442k  
    OP
       311 天前
    @qhkobold 老项目 java8,我自己项目的话,我就直接 new Thread().start 了 哈哈
    baolinliu442k
        27
    baolinliu442k  
    OP
       311 天前
    @chendy 嗯嗯,公司的项目我还不敢随便配
    baolinliu442k
        28
    baolinliu442k  
    OP
       311 天前
    @blankmiss 哈哈不知道
    baolinliu442k
        29
    baolinliu442k  
    OP
       311 天前
    @joyhub2140 就是不知道用啥感觉
    baolinliu442k
        30
    baolinliu442k  
    OP
       311 天前
    @kuituosi 感谢回答,就是感觉 Node 单线程可以很方便进行异步挺好的,而且我也不知道啥算 CPU 密集型
    baolinliu442k
        31
    baolinliu442k  
    OP
       311 天前
    @CodeCodeStudy 嗯嗯,可是项目中定义线程池还是蛮普遍的
    baolinliu442k
        32
    baolinliu442k  
    OP
       311 天前
    @oneronan 感谢回答
    baolinliu442k
        33
    baolinliu442k  
    OP
       311 天前
    @Aresxue 谢谢回答, 我还有个疑问如果一个项目中定义了多个线程池,例如 2 个线程池,核心线程数都是 5 ,机器 cpu 核数是 5 , 那么可以同时执行 10 个任务吗? 线程池定义多了是不是作用不大了
    baolinliu442k
        34
    baolinliu442k  
    OP
       311 天前
    @nothingistrue 大佬,我茅厕顿开
    baolinliu442k
        35
    baolinliu442k  
    OP
       311 天前
    @Seulgi 确实需要慎用, 目前公司做的 toB 业务,一个大接口响应 10s 都不要优化
    baolinliu442k
        36
    baolinliu442k  
    OP
       311 天前
    @imokkkk 谢谢回答,感谢贴出代码, 很有参考意义
    baolinliu442k
        37
    baolinliu442k  
    OP
       311 天前
    @TuringHero 感觉 java 已经很先进了, 然而项目还是 java8hah
    baolinliu442k
        38
    baolinliu442k  
    OP
       311 天前
    @hdfg159 这么爽的嘛
    ymy3232
        39
    ymy3232  
       311 天前
    我们线上业务不复杂但是并发高而且时长要求严格,项目全局用一个公共的线程池,包括 springweb 和 CompleteableFuture 等一些组件的默认线程池都替换了,好处坏处都有,视项目而定
    Plutooo
        40
    Plutooo  
       311 天前
    可以看一下 rocketmq 源码里面是如何使用线程池的,全局搜索一下就可以
    Aresxue
        41
    Aresxue  
       310 天前
    @baolinliu442k 并发度和 cpu 核数有关系但没太大关系,cpu 哪怕是单核因为时间片是轮转的从使用者视角来看任务都是并行的,回到你这个问题从普通的使用者视角可以认为同时执行 10 个任务,但如果是按真实的占用 cpu 去执行逻辑这个角度,哪怕你有 5 个核,这 5 个核同时被你的任务使用的时间几乎是没有的,因为还有 tomcat 线程、rpc 线程池等其它活跃的线程共享你的 5 个核。
    Aresxue
        42
    Aresxue  
       310 天前
    @Aresxue 其实可以把虚拟线程学起来了,有了这玩意之后绝大多数场景就不再需要线程池了,线程不会再成为应用的瓶颈,目前 jdk21 中的功能已经勉强可用,预计下个 LTS 版本能基本稳定下来。
    java123
        43
    java123  
       309 天前
    Vert.x 、Parseq
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1022 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 21:27 · PVG 05:27 · LAX 13:27 · JFK 16:27
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.