线程池在项目中怎么使用的疑惑

2019-06-05 11:35:14 +08:00
 dovme
在网上搜索,都只会讲类似使用

ExecutorService s = Executors.newFixedThreadPool(5);

这种方式来创建

我想问的是: 在一个请求需要处理很复杂的运算时,使用线程池,

那么是直接在方法里面 new 一个出来,使用完之后关闭掉?

还是使用单例模式创建一个全局的线程池.

我不是很理解这个

如果说每次请求 new 一个线程池出来的话,

在高并发下,是不是存在很多个线程池,那么内存应该会溢出的啊.

但如果只创建一个线程池,

在高并发下,无数的请求都排队使用那几个固定的线程,不是更慢了吗?

java 里面每个 web 请求过来都是一个线程,不使用线程池的话,

自己的线程处理自己的事情,比所有请求共用一个线程池快得多吧.

我不知道我这么想对不对.但是我使用 apache 的 ab 同时请求 1000 次的话,

不使用线程池比使用单例的线程池快很多很多.

希望大佬来帮小弟解惑.
6519 次点击
所在节点    Java
20 条回复
gosansam
2019-06-05 11:48:51 +08:00
#### 首先用 Spring 创建一个线程池的 Bean 例如 taskExecutor,需要异步处理的方法上添加 @Async("taskExecutor"),这样调用异步方法就会使用这个线程池。
#### 线程池这个东西肯定不是需要就 new 一个,为什么使用线程池? 为了节省线程创建和销毁带来的消耗,每次 new 一个线程池比每次 new 一个线程开销更大。
#### 高并发下,需要根据机器配置设计合理的线程池参数,使用线程池是为了异步部分请求,加速全局请求。
dovme
2019-06-05 11:59:56 +08:00
@gosansam #1 使用 @Async 注解 方法并发执行,还需要线程池吗?还是说防止异步线程太多,才使用线程池?
dovme
2019-06-05 12:03:05 +08:00
@dovme #2 所以说我理解错了,线程池一般都是异步执行,而不是同步对吗?
pursuer
2019-06-05 12:11:21 +08:00
怕线程不够就不要创建固定线程池,用动态增长的线程池(好像叫 CachedThreadPool?)不就可以了
cxtrinityy
2019-06-05 12:18:57 +08:00
线程池也有很多种的,1L 也说了,主要是为了节省创建线程的开销,复用已创建线程,用的时候一般都是全局 new 1 个或者你有特殊需求 new N 个
比如你举的例子,是固定线程数量为 5 的线程池,不管几个 runnable,callable 过来,同时跑的线程只有 5 个,其他的都得排队,还有 newCache (按需创建,默认线程数量上限整形上届,不保存固定数量线程,idle 线程存活 60 秒)、newSingle (单线程复用)等等,可以看看
至于你说的 1000 个请求不使用线程池比较快,得具体情况具体分析,如果你用的是像你举的例子那样,那肯定是按需 new 线程快,毕竟线程池里只有 5 个线程在并发
dovme
2019-06-05 12:40:47 +08:00
@cxtrinityy #5 使用 newCache 这个创建的线程池,如果 1000 个请求过来,会创建 1000 个线程?目前 4 vCPU 8 GiB 内存这样的服务器,能承受多少线程而不出什么问题呢?
chendy
2019-06-05 12:58:34 +08:00
snappyone
2019-06-05 13:19:06 +08:00
@dovme 最佳线程数取决于你要处理的任务类型和你的 cpu 数量,简单的说 cpu 密集型任务就跟 cpu 数量差不多的线程数就可以了,而 io 密集型则线程可以设置比较大(具体多大需要测试才知道),线程创建太多会导致 cpu 上下文切换造成额外无必要的性能开销
Beeethoven
2019-06-05 13:39:00 +08:00
我理解线程池并不是一个解决高并发的好方法。一般我都是在后台处理复杂数据时用的,接收到请求时,主线程处理简单的部分并返回结果,复杂的部分开个新线程扔过去慢慢处理。
cxtrinityy
2019-06-05 13:40:14 +08:00
@dovme 不一定会有 1000 个,因为如果某个线程执行完任务就会被复用
至于内存方面,没有具体计算过,不过一个具体线程并不会占用太多内存,具体到 runnable 看实现,这些都可以通过类内定义的变量来计算的,不过 1000 个对象应该没什么压力
至于 CPU,楼上说的挺清楚了
f2ed
2019-06-05 13:44:35 +08:00
当然是创建 CPU 核心数量的线程数了
Takamine
2019-06-05 13:51:53 +08:00
线程池是为了节省频繁创建的开销,另外一方面是对系统资源稳定的一种保护。
一般线程数取计算密集型 N+1,IO 密集型 2N,然后再调整,可以查一下公式。
简单计算就是 线程数= cpu 数 /( 1-阻塞率)。
axbx
2019-06-05 14:07:55 +08:00
一般用线程池是为了避免异步处理任务的时候重复创建线程的开销,使用 TheadPoolTaskExectour 来创建线程池 Bean,整个项目都用这个 Bean 创建线程。
0xZhangKe
2019-06-05 14:10:12 +08:00
首先线程池是用来解决线程共用问题的,此外不同的线程池解决的问题多少有些不同。
面对这样的设定,我们可以设想一下不同的线程池可以解决什么样的问题。
例如我们可以创建一个线程数固定为 1 的全局单利线程池,用来执行一些优先级实时性都不高,但可能个数较多的任务。比如定期收集日志并上传、获取设备内存等状态信息、检测并清理运行时的垃圾文件等等。
同样,可以创建一个固定个数为 CPU 核心数的全局单利线程池,用来执行一些实时性要求较高,但也没这么高的任务,例如网络请求,网络请求框架 Volley 使用的就是个数为 4 的全局单利线程数组来执行网络请求。
ligz
2019-06-05 14:24:36 +08:00
谈谈我的理解。首先一般不使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方更加明确线程池的运行规则,规避资源耗尽的风险。

其次,肯定不是需要的时候就 new 一个出来,而是通过全局配置的线程池,有这么个作用
1. 降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2. 提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。

你需要设定核心线程数和最大线程数,一般根据你 cpu 的核数和是 IO 型的任务还是 CPU 型的任务决定,不会无限制的创建线程的,多余的任务存储在你设置的队列里面,比如阻塞队列 BlockingQueue。

真正执行计算逻辑的还是你操作系统的线程,当你的任务操作时间很短或者数量很少的时候,看不出什么区别,甚至会更慢。如果你没提前创建好线程池,线程池的创建时间可能比你执行那些请求的时间都长。
shangfabao
2019-06-05 14:25:05 +08:00
看你自己的使用场景了,大部分是固定几个线程池
qiyuey
2019-06-05 14:29:52 +08:00
线程池的问题在于你需要估计线程池的配置,注意是估计,应为线程池的配置其实是没办法准确计算的,需要通过压测来不断调整。即使配置相对合理之后,仍不能避免线程阻塞导致的线程切换的成本。不如直接用 Coroutines 和 Reactive。
dovme
2019-06-05 14:41:57 +08:00
@ligz #15
@0xZhangKe #14
@cxtrinityy #10
@qiyuey #17
@axbx #13
@gosansam #1 谢谢大佬们详细的解答.非常感谢
securityCoding
2019-06-05 15:06:56 +08:00
spring 中的 taskExecutor bean 就是单例的,跟你自己创建差不多
x7395759
2019-06-05 17:31:08 +08:00
Java 并发实战推荐给你

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

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

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

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

© 2021 V2EX