其他系列文章目录
文章目录
前言
学习线程池能够帮助我们更好地处理多线程编程,并提高程序的性能和稳定性。
一、为什么需要线程池?
JVM在HotSpot的线程模型下,Java线程会一对一映射为内核线程。
这意味着,在Java中每次创建以及回收线程都会去内核创建以及回收。
这就有可能导致: 创建和销毁线程所花费的时间和资源可能比处理的任务花费的时间和资源要更多。
线程池的出现是为了提高线程的复用性以及固定线程的数量!!!
二、举个背景例子
一个消息管理平台,提供其中一个功能就是: 运营会圈定人群然后群发消息。
主要流程大致就是:创建模板---->定时---->群发消息---->用户收到消息。
先说一个概念:HDFS (Hadoop Distributed File System) 是分布式文件系统的一种,用于存储和处理大数据集。它是Hadoop框架的核心组件之一。HDFS可以让用户将大数据集分散在多台计算机上,以提高数据处理能力和可靠性。它将数据拆分为小块并存储在多台计算机上,提供了读写分离、容错机制、数据备份和高可用等特性。HDFS的基本单位是数据块,通常为128MB或256MB。数据块的复制数量和位置由系统自动管理。HDFS对于大数据的存储和管理提供了高度的可靠性和可扩展性,因此被广泛应用于大数据处理和分析领域。
然后运营圈定的人群实际上在模板上只是一个ID、这边要通过ID去获取到HDFS文件,
对HDFS文件进行遍历,然后继续往下发,(接收到定时任务,再对HDFS进行遍历)这里的处理,用的就是线程池处理。
HDFS遍历其实就是IO的操作,把这个过程给异步化,为了提高系统的吞吐量,于是这里用的线程池。即便遍历HDFS出现问题,我们可以建设完备的监控和告警可以及时发现。
三、怎么创建线程池?
阿里巴巴开发手册就有提到,不要使用Executors去创建线程。建议使用ThreadPoolExecutor去创建线程池。
最主要的目的就是:使用ThreadPoolExecutor创建的线程你是更能了解线程池运行的规则,避免资源耗尽的风险。
七个核心参数:
- corePoolSize:核心线程数,线程池维护的最少线程数。
- maximumPoolSize:最大线程数,线程池维护的最大线程数。
- keepAliveTime:线程空闲时间,当线程池中的线程数大于核心线程数时,这些多余的线程会被销毁,这个参数定义了这些线程的空闲时间。
- TimeUnit:时间单位,keepAliveTime的时间单位。
- workQueue:任务队列,当提交的任务数量大于线程池当前可用的线程数时,任务会被存放在这个队列中。
- threadFactory:线程工厂,用于创建线程池中的线程。
- handler:拒绝策略,当任务队列已满且线程池中的线程数量已达到最大值时,新提交的任务如何被拒绝执行。常用的拒绝策略有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy和DiscardPolicy。
任务提交流程:
- 首先会判断运行线程数是否小于coreFoolSize,如果小于,则直接创建新的线程执行任务。
- 如果大于corePoolSize,判断workQueue阻塞队列是否已满,如果还没满,则将任务放到阻塞队列中。
- 如果workQueue阻塞队列已经满了则判断当前线程数是否大于maximumPoolSize,如果没大于则创建新的线程执行任务。
- 如果大于maximumPoolSize,则执行任务拒绝策略 (具体就是你自己实现的handler)。
这里有个点需要注意下,就是workQueue阻塞队列满了,但当前线程数小于maximumPoolSize,这时候会创建新的线程执行任务。
不过一般我们都是将corePoolSize和maximumPoolSize设置相同数量。
keepAliveTime指的就是,当前运行的线程数大于核心线程数了,只要空闲时间达到了,就会对线程进行回收。
四、线程池指定线程数
线程池指定线程数这块,首先要考量自己的业务是什么样的??
是cpu密集型的还是io密集型的,假设运行应用的机器CPU核心数是N。
cpu密集型的可以先给到N+1,io密集型的可以给到2N 。
上面这个只是一个常见的经验做法,具体究竟开多少线程,需要压测才能比较准确地定下来。
注:线程不是说越大越好,在之前的我也提到过,多线程是为了充分利用CPU的资源。如果设置的线程过多,线程大量有上下文切换,这一部分也会带来系统的开销,这就得不偿失了。