一、前言
我们收银台项目为了架构简单,一些异步化任务(如下单完成扣减库存、发送邮件等等),没有使用MQ而是直接启动线程来做,但如果随意使用线程对系统性能反而会有影响,当线程数量大到一定的时候会耗尽CPU和内存资源,并且创建和销毁线程也需要时间,大量线程回收会造成GC的压力,所以一般项目中都统一使用一个线程池集中管理所有线程的创建和回收。
注:因为今天发现项目中使用了两个线程池,做了优化后所以有了这篇文章。
二、线程和线程池对比例子
注:直接使用线程,在我本机运行创建了2000多个线程然后直接OOM了。
注:使用线程池,没有产生OOM,每次只有10个线程在运行,其它放在线程池的任务队列中等待执行。
三、CPU
-
物理CPU数(physical cpu)):指主板上实际插入物理CPU个数。
cat /proc/cpuinfo | grep "physical id" | sort | uniq | wc -l
-
核数(Core):在单个物理CPU增加核心,也就是出现双核或多核,这样一个双核CPU同一时间可以运行两个进程/线程。
cat /proc/cpuinfo | grep "cpu cores" | uniq
-
逻辑处理器数(Processor):在单个core为了提高处理能力逻辑上划分为多个。
cat /proc/cpuinfo | grep "processor" | wc -l
获取JVM可用的核心数
Runtime.getRuntime().availableProcessors()
线程任务分为以下两类,
-
CPU密集型
CPU密集型任务有大量计算,消耗CPU资源,比如视频的编解码这种都是比较耗时间的操作,线程任务越多花在任务切换的时间就越多,应该减少线程的数量,CPU密集型任务线程数应该等于CPU的核心数。
-
IO密集型
IO密集型CPU消耗比较少,大部分时间都在等待IO,线程越多效率越高但也有限度,我们一般应用都是属于这种,核心线程数=CPU核心数*2.
四、线程池配置例子
线程池配置参数说明
-
corePoolSize-核心线程数
核心线程即使没有任务执行也会一直存活,当线程数小于核心线程数即使有空闲的线程也会创建新的线程以尽量达到核心线程数。注:这和连接中的最小连接数不一样。
-
queueCapacity-任务队列容量
当核心线程数达到最大时,新的任务会放在队列中等待执行,即当你设置的队列容量比较大时,一般活动的线程数会等于或小于核心线程数。
-
maxPoolSize-最大线程数
当线程数大于corePoolSize且任务队列已满,线程池会创建新的线程来处理。
当线程数等于maxPoolSize且任务队列已满,线程池会抛出异常拒绝执行任务。
-
keepAliveSeconds-线程空闲时间
当线程空闲时间达到设置的值,线程会自动退出。如果设置了allowCoreThreadTimeout=true,则核心线程空闲也会退出。
线程池使用例子
ThreadPoolTaskExecutor执行顺序
1、当线程数小于corePoolSize,创建新的线程。
2、当线程数大于等于corePoolSize,且任务队列未满时,将任务放入任务队列
3、当线程数大于corePoolSize,且任务队列已满时,
若线程数小于maxPoolSize,则创建线程
若线程数等于maxPoolSize,则拒绝任务,抛出异常。
ThreadPoolExecutor.java对应核心源代码如下。