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利用率

  1. 高并发、执行任务时间短的业务

    • 执行任务时间短,我们不需要去考虑是CPU密集型还是IO密集型,反正执行时间短

    • 线程数可以设置为CPU核数+1,减少线程上下文切换

    • 针对JDK线程池,核心线程数跟可以为0,创建出来的全部都是救急线程,可以根据一定时间的空闲让线程自动结束,最大线程数根据需要进行设计,阻塞队列可以采用同步阻塞队列,但是最大线程数必须设计合理,因为使用同步阻塞队列,没有线程来取,任务是放不进去的,可以通过调高最大线程数,或者换成LinkedBlockingQueue

    • // JDK线程池
      new ThreadPoolExecutor(0, 10,
                             60L, TimeUnit.SECONDS,
                             new SynchronousQueue<Runnable>());
      
  2. 并发不高,任务执行时间长,要区分成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线程池设置线程数大小的全部内容,如有解析不当欢迎在评论区指出!

你可能感兴趣的:(java,开发语言,网络)