实际项目中如何使用线程池 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
baolinliu442k
V2EX    Java

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

  •  1
     
  •   baolinliu442k 2024-01-07 19:35:57 +08:00 5819 次点击
    这是一个创建于 644 天前的主题,其中的信息可能已经有所发展或是发生改变。

    工作后,接触过定义线程池的情况只有全局一个线程池,由前辈设置,自己用就可以了。再就是使用 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  
       2024-01-07 20:03:30 +08:00
    不想打字,你可以选择直接问 gpt ,它比我们大部分人说的明白
    baolinliu442k
        2
    baolinliu442k  
    OP
       2024-01-07 21:08:53 +08:00
    以下是来自 chatgpt 的回答
    1. 线程池定义在哪里?
    线程池的定义通常应该放在一个全局的工具类(如 ThreadPoolUtil )或者专门的配置类中。这样做有助于集中管理和配置线程池,提高代码的可维护性。在业务类中定义线程池(例如在 xxxService 的 private static )通常不是最佳实践,因为线程池的配置和管理应该是全局的,而不仅仅局限于某个业务类。

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

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

    4. 执行任务的方式?
    在使用线程池执行任务时,可以使用 submit 方法或 execute 方法,具体选择取决于你对任务执行结果的需求。
    w741069229
        3
    w741069229  
       2024-01-07 23:37:25 +08:00 via iPhone   1
    谨慎用在生产
    Leviathann
        4
    Leviathann  
       2024-01-07 23:41:44 +08:00
    异步和多线程本来就是两回事

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

    线程池用的最多的是后端框架和客户端请求池,你没看错客户端也用的很多,而且频繁程度很高,http 异步也需要一个线程池管理着。
    qhkobold
        10
    qhkobold  
       2024-01-08 09:56:34 +08:00
    用啥线程池呢,直接上 jdk21 用虚拟线程
    mmdsun
        11
    mmdsun  
       2024-01-08 09:57:57 +08:00
    线程池 Spring 配置 bean 用的时候注入 + CompletableFuture
    Seulgi
        12
    Seulgi  
       2024-01-08 10:24:18 +08:00   1
    全局,不管 util 类还是 spring bean 管理,都是其中一种实现方式。按需,小组自己评估哪些任务可以丢到哪些池里去,相当于给池定义他的领域,属于他领域的就用他,谁用了池,哪里用了池,要评估,不要一个人随便写个池,别人也随便在随便用你的池。慎用,除非没有其他办法,一般不用池。现在这个时间,一般公司用的都是 future 。Java21 的虚拟线程,现在可以忽略。生产项目都还没几个 Java21 实践。
    kuituosi
        13
    kuituosi  
       2024-01-08 10:50:27 +08:00   1
    1.定义在合适的地方,要看具体的需求
    2.同一类的任务放一个线程池,防止互相干扰,也便于优化
    3.减少依赖和干扰,优化执行效率肯定提升性能
    4.肯定看情况使用,CompletableFuture 最强大,不仅获取异步结果,而且可以设置超时和组合
    java 的多线程已经提供足够好的库已经足够好用了,能够跟 java 多线程比的也就是协程了
    node 这种半残语言只有一个线程,根本没有多线程。只适合 io 型工作,遇到 cpu 型就是废物。
    不要提可以多进程 node 也能做 cpu 型工作,不仅麻烦不好维护,也难于优化
    而 java 的多线程有优秀的框架,使用也比较简单,工程师更多时候花在优化参数上,而且可以胜任复杂的业务场景
    node 跟 java 比完全没有一战之力
    CodeCodeStudy
        14
    CodeCodeStudy  
       2024-01-08 10:56:41 +08:00
    底层类库用的,业务中谨慎使用
    oneronan
        15
    oneronan  
       2024-01-08 11:28:46 +08:00
    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  
       2024-01-08 14:37:25 +08:00
    1.视业务而定,如果是一个低频的业务和其它业务共享一个线程池也无伤大雅,如果相对并发较高,最好是指定用自己的线程池而不是公用的线程池,@Async 也是可以指定线程池的,和 private static 的方式基本上是等价的,大多数情况下都可以用它;
    2.参数没有标准,完完全全根据业务的情况而定,这个情况不仅是当下还有对未来的适当评估;
    3.线程池主要是用于隔离线程资源和多参数任务并行降低 rt ,其对于整个应用资源的利用率并不会有显著的提升;
    4. execute 适用于没有返回值的任务,submit 的返回值是 Future ,基本上能用 submit 没啥必要了,CompleteableFuture 本来就是为了加强 Future 的。

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

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

    异步执行的原理就是这么复杂,你所谓的优雅,不是优雅,只是隐藏了底层 API ,同时也失去了定制能力的傻瓜式 API 而已。
    imokkkk
        18
    imokkkk  
       2024-01-08 15:19:26 +08:00
    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  
       2024-01-08 15:23:02 +08:00
    注意区分业务场景,举个例子,你就明白了,有 2 个服务,A 调用 B ,B 服务有几次 mysql 超时,A 调用 B 的线程池,因为 B 的超时,全部等待,然后 A---挂了。(事后 B 还嘲讽了 A 的负责人,然后群里吵起来了....) 这个应该算生产比较容易出故障的场景
    matepi
        20
    matepi  
       2024-01-08 15:56:26 +08:00
    @northernsongy2 没有 SLA 就是这样的啦。其实不给 SLA ,A 也有个办法就是坏一个丢一个(池扩张一),反正 B 的问题。
    GloryJie
        21
    GloryJie  
       2024-01-08 17:45:44 +08:00
    在 BFF 层聚合数据的时候用的多,一般会同时调好几个接口。这边使用上线程池一般都托管给 Spring 。
    最近在改造成基于 Dag 来编排任务执行了,不过基础还是让线程池执行
    TuringHero
        22
    TuringHero  
       2024-01-08 17:54:02 +08:00
    spring:
    threads:
    virtual:
    enabled: true
    hdfg159
        23
    hdfg159  
       2024-01-08 19:34:35 +08:00
    Spring Boot 3.2 打开虚拟线程开关,你都不用管了,直接 @Async 注解无脑用了
    baolinliu442k
        24
    baolinliu442k  
    OP
       2024-01-08 19:36:55 +08:00
    @siweipancc 没有,觉得 await 和 async 关键字挺好的
    baolinliu442k
        25
    baolinliu442k  
    OP
       2024-01-08 19:37:22 +08:00
    @ffw5b7 这篇之前也看过
    baolinliu442k
        26
    baolinliu442k  
    OP
       2024-01-08 19:38:32 +08:00
    @qhkobold 老项目 java8,我自己项目的话,我就直接 new Thread().start 了 哈哈
    baolinliu442k
        27
    baolinliu442k  
    OP
       2024-01-08 19:39:40 +08:00
    @chendy 嗯嗯,公司的项目我还不敢随便配
    baolinliu442k
        28
    baolinliu442k  
    OP
       2024-01-08 19:40:06 +08:00
    @blankmiss 哈哈不知道
    baolinliu442k
        29
    baolinliu442k  
    OP
       2024-01-08 19:40:31 +08:00
    @joyhub2140 就是不知道用啥感觉
    baolinliu442k
        30
    baolinliu442k  
    OP
       2024-01-08 19:44:37 +08:00
    @kuituosi 感谢回答,就是感觉 Node 单线程可以很方便进行异步挺好的,而且我也不知道啥算 CPU 密集型
    baolinliu442k
        31
    baolinliu442k  
    OP
       2024-01-08 19:45:13 +08:00
    @CodeCodeStudy 嗯嗯,可是项目中定义线程池还是蛮普遍的
    baolinliu442k
        32
    baolinliu442k  
    OP
       2024-01-08 19:48:12 +08:00
    @oneronan 感谢回答
    baolinliu442k
        33
    baolinliu442k  
    OP
       2024-01-08 19:53:07 +08:00
    @Aresxue 谢谢回答, 我还有个疑问如果一个项目中定义了多个线程池,例如 2 个线程池,核心线程数都是 5 ,机器 cpu 核数是 5 , 那么可以同时执行 10 个任务吗? 线程池定义多了是不是作用不大了
    baolinliu442k
        34
    baolinliu442k  
    OP
       2024-01-08 19:55:23 +08:00
    @nothingistrue 大佬,我茅厕顿开
    baolinliu442k
        35
    baolinliu442k  
    OP
       2024-01-08 19:57:55 +08:00
    @Seulgi 确实需要慎用, 目前公司做的 toB 业务,一个大接口响应 10s 都不要优化
    baolinliu442k
        36
    baolinliu442k  
    OP
       2024-01-08 20:02:14 +08:00
    @imokkkk 谢谢回答,感谢贴出代码, 很有参考意义
    baolinliu442k
        37
    baolinliu442k  
    OP
       2024-01-08 20:10:28 +08:00
    @TuringHero 感觉 java 已经很先进了, 然而项目还是 java8hah
    baolinliu442k
        38
    baolinliu442k  
    OP
       2024-01-08 20:11:15 +08:00
    @hdfg159 这么爽的嘛
    ymy3232
        39
    ymy3232  
       2024-01-08 21:41:09 +08:00
    我们线上业务不复杂但是并发高而且时长要求严格,项目全局用一个公共的线程池,包括 springweb 和 CompleteableFuture 等一些组件的默认线程池都替换了,好处坏处都有,视项目而定
    Plutooo
        40
    Plutooo  
       2024-01-08 22:42:44 +08:00
    可以看一下 rocketmq 源码里面是如何使用线程池的,全局搜索一下就可以
    Aresxue
        41
    Aresxue  
       2024-01-09 10:03:15 +08:00
    @baolinliu442k 并发度和 cpu 核数有关系但没太大关系,cpu 哪怕是单核因为时间片是轮转的从使用者视角来看任务都是并行的,回到你这个问题从普通的使用者视角可以认为同时执行 10 个任务,但如果是按真实的占用 cpu 去执行逻辑这个角度,哪怕你有 5 个核,这 5 个核同时被你的任务使用的时间几乎是没有的,因为还有 tomcat 线程、rpc 线程池等其它活跃的线程共享你的 5 个核。
    Aresxue
        42
    Aresxue  
       2024-01-09 10:05:47 +08:00
    @Aresxue 其实可以把虚拟线程学起来了,有了这玩意之后绝大多数场景就不再需要线程池了,线程不会再成为应用的瓶颈,目前 jdk21 中的功能已经勉强可用,预计下个 LTS 版本能基本稳定下来。
    java123
        43
    java123  
       2024-01-10 08:42:48 +08:00
    Vert.x 、Parseq
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2205 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 92ms UTC 16:08 PVG 00:08 LAX 09:08 JFK 12:08
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86