JDK线程池中到底该设置多少线程数才比较合适
JDK线程池中到底该设置多少线程数呢?
- 为什么线程数太多会导致频繁的上下文切换呢?
- 通过判断业务是CPU密集型还是IO密集型去决定线程数的大小
- 1、CPU密集型
- 2、IO密集型
- 如何根据业务特点去计算线程数的大小呢?
很多小伙伴觉得,线程池中设置多少线程数,或者说创建的JDK线程池中核心线程数、最大线程数该设置多少比较合适,一般都是通过CPU核数去计算的,但是需要根据处理业务的特点去进行设计
- 线程数太少会导致程序不能充分利用系统资源、容易让阻塞队列里边的任务出现饥饿的现象
- 线程数太多会导致频繁的线程上下文切换,消耗cpu资源并且会占用更多的内存
- IO操作是数据的交互(input跟output)需要从硬盘中读取数据或者网络请求获取数据,将数据读到内存后让CPU处理,读数据的过程中不会占用CPU,但是当前线程需要通过IO获取数据,所以必须等到IO操作结束后再交由CPU处理,此时会导致线程阻塞。也就是说IO操作不会占用CPU但是会阻塞线程
为什么线程数太多会导致频繁的上下文切换呢?
基于JDK线程池,它的工作原理就是维护两个集合,workersSet和taskQueue,workersSet中的worker不断从任务队列中取出任务去执行,如果线程数太多
- 核心线程取不到阻塞队列中的任务,底层通过LockSupport.park(),将当前线程状态从Runnable转换为WAITING,此时会导致线程上下文切换。
- 非核心线程数也就是救急线程数(最大线程数-核心线程数)也会有线程状态从Runnable转换为WAITING的过程,并且在一定时间内结束线程
通过判断业务是CPU密集型还是IO密集型去决定线程数的大小
1、CPU密集型
- 需要CPU大量计算,很少有IO操作比如查询数据库、Redis或者RPC、HTTP调用
- 通常采用CPU核数+1能够实现最优的CPU利用率,+1是保证当线程由于缺页故障(操作系统)或其他原因导致暂停时,额外的这个线程就能顶上,保证CPU时钟周期不被浪费
2、IO密集型
- 有很多查询数据库或者Redis、RPC、HTTP请求,包括读取文件等
- 执行IO请求时,不占用CPU但是占用线程,此时就需要多个线程去提高利用率
- 线程数 = 核数 * 期望CPU利用率 * 总时间(CPU计算时间+等待时间)/ CPU 计算时间
如何根据业务特点去计算线程数的大小呢?
业务中如果出现到使用线程池的场景,无非就是出现高并发或者任务执行时间长,需要通过多个线程去提高CPU利用率
-
高并发、执行任务时间短的业务
-
执行任务时间短,我们不需要去考虑是CPU密集型还是IO密集型,反正执行时间短
-
线程数可以设置为CPU核数+1,减少线程上下文切换
-
针对JDK线程池,核心线程数跟可以为0,创建出来的全部都是救急线程,可以根据一定时间的空闲让线程自动结束,最大线程数根据需要进行设计,阻塞队列可以采用同步阻塞队列,但是最大线程数必须设计合理,因为使用同步阻塞队列,没有线程来取,任务是放不进去的,可以通过调高最大线程数,或者换成LinkedBlockingQueue
-
// JDK线程池 new ThreadPoolExecutor(0, 10, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
-
-
并发不高,任务执行时间长,要区分成CPU密集型或者IO密集型
-
CPU密集型
- 假如业务执行时间主要花费在计算上,那么就是CPU密集型任务,那么还是要减少线程数,减少线程上下文切换
-
IO密集型
-
假如业务执行时间集中在IO操作室,那么就是IO密集型任务,因为IO操作不会占用CPU,通过加大线程数,让CPU处理更多的业务,充分利用CPU
-
JDK线程池,可以不需要设置救急线程,适用于任务量已知,相对耗时的任务
-
new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
-
-
- 并发高,任务执行时间长的业务,解决这种类型的业务除了要设计好线程池,还需要设计好整体的架构,这里提供一些思路
- 并发请求同步转异步,设计线程池
- 如果查询数据库时间长
- 是否可以优化sql,例如:limit查询要避免无效回表查询,连表查询慢可以通过设计中间表等
- 适当创建索引,通过记录慢查询、explain查看执行计划,判断索引是否失效
- sql不能够在优化,但是查询速度还是很慢,看看单表数据是否太大,超过200w就算数据量很多,或者数据库的QPS超过2000,就需要进行分库分表,或者搭建主从集群,利用ShardingJDBC或者Mycat进行分库分表,通过实现读写分离减轻主库压力,提高查询效率
- 网络带宽太小
- 优化代码结构,减少不必要的类创建或者查询
- 设计多级缓存
- 最终才是通过增加服务器,通过横向拓展的方式去提高整个系统的QPS
以上便是JDK线程池设置线程数大小的全部内容,如有解析不当欢迎在评论区指出!