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

319 天前
 baolinliu442k

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

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

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

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

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

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

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

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

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

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

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

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

异步执行的原理就是这么复杂,你所谓的优雅,不是优雅,只是隐藏了底层 API ,同时也失去了定制能力的傻瓜式 API 而已。
imokkkk
318 天前
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
318 天前
注意区分业务场景,举个例子,你就明白了,有 2 个服务,A 调用 B ,B 服务有几次 mysql 超时,A 调用 B 的线程池,因为 B 的超时,全部等待,然后 A---挂了。(事后 B 还嘲讽了 A 的负责人,然后群里吵起来了....) 这个应该算生产比较容易出故障的场景
matepi
318 天前
@northernsongy2 没有 SLA 就是这样的啦。其实不给 SLA ,A 也有个办法就是坏一个丢一个(池扩张一),反正 B 的问题。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1006600

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX