线程池浅析
2021-05-02 16:30
标签:syn 并且 cut locking 介绍 schedule ima 排队 任务
线程池优势
线程池主要是控制运行的线程数量,处理过程中将任务放入队列,然后再线程创建后启动这些任务,如果线程数量超过最大数量,超出数量的线程排队等候,等待其他线程执行完毕,再从队列中取出来执行。
优势: 线程复用,控制最大并发数,管理线程。
- 降低资源消耗。通过服用利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的客观理性。线程是稀缺资源,如果无限制创建,不仅回消耗系统资源,还会降低系统稳定性,使用线程池可以进行统一分配,调优和监控。
线程池实现
Java中线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类。
实现方式
Executors.newFixedThreadPool(int nThreads)
- 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
-
newFixedThreadPool
创建的线程池corePoolSize
和maximumPoolSize
值是相等的,它使用的是LinkedBlockingQueue
。
适用场景
执行长期的任务,性能好很多。
Executors.newSingleThreadExecutor()
- 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。
-
newSingleThreadExecutor
创建的线程池corePoolSize
和maximumPoolSize
值设置为1,它使用的是LinkedBlockingQueue
。
适用场景
任务顺序执行的场景。
Executors.newCachedThreadPool()
- 创建一个可缓存线程池,如果线程池长度超过处理需求,可灵活回收空闲线程,若无可回收,则新建线程。
-
newCachedThreadPool
创建的线程池corePoolSize
设置为0,将maximumPoolSize
设置为Integer.MAX_VALUE
,它使用的是SynchronousQueue
,也就是说,有任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
适用场景
执行很多短期异步的小程序或者负载较轻的服务器。
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样创建方式可有效规避资源耗尽的风险。
说明:Executors创建线程池对象的弊端如下:
- FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
- CachedThreadPool和ScheduledThreadPool:允许创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
线程池7参介绍
corePoolSize
线程池中的常驻核心线程数。
创建线程池后,当有请求任务之后,会安排线程池中的线程去执行请求任务,近似理解为当值线程。
当线程池中的线程数目达到corePoolSize
后,就会把到达的任务放到队列当中。
maximumPoolSize
线程池能容纳同时执行的最大线程数,此值必须大于等于1。
keepAliveTime
多余的空闲线程存活时间。当前线程池数量超过corePoolSize
时,当空闲时间达到keepAliveTime
值时,多余空闲线程会被销毁,直到只剩下corePoolSize
个线程为止。
unit
keepAliveTime
的单位。
workQueue
任务队列,存放被提交但尚未被执行的任务。
threadFactory
生成线程池中工作线程的线程工厂,用于创建线程,一般用默认的即可。
defaultHandler
拒绝策略,表示当队列已经存满,并且工作线程大于等于线程池的最大线程数(maximumPoolSize
)时,如何来拒绝请求执行的runnable的策略。
案例对比
线程池银行网点
corePoolSize
今日当值窗口
maximumPoolSize
银行网点业务办理窗口的最大数
workQueue
网点候客区
threadFactory
银行网点的管理人员,负责窗口调度窗口办理业务人员的管理员
keepAliveTime
银行网点所有窗口都在办理业务,但是已无客流量,工作不饱和状态的时间
unit
工作不饱和状态的时间单位
defaultHandler
银行网点满负荷状态(业务办理窗口满负荷,候客区满负荷)下,采取的应急策略
线程池底层工作原理
执行流程
-
在创建线程池后,等待提交的任务请求。
-
当调用
execute()
方法添加一个请求任务时,线程池会做如下判断:2.1 如果正在运行的线程数量小于
corePoolSize
,那么马上创建线程运行这个任务。2.2 如果正在运行的线程数量大于或等于
corePoolSize
,那么将这个任务放入队列。2.3 如果这时候队列已经满载且正在运行的线程数量小于
maximumPoolSize
,那么还是要创建非核心线程立即运行这个任务。2.4 如果队列已经满载且正在运行的线程数量大于或等于
maximumPoolSize
,那么线程池会启动饱和拒绝策略来执行。 -
当一个线程完成任务时,它会从队列中取下一个任务来执行。
-
当一个线程无事可做超过一定的时间(
keepAliveTime
)时,线程池会判断:如果当前运行的线程数量大于
corePoolSize
,那么这个线程就会被停掉。所以线程池的所有任务完成后他最终会收缩到corePoolSize
的大小。
线程池的拒绝策略
等待队列已经满负荷,同时线程池中的maximumPoolSize
也达到顶峰,这个时候需要拒绝策略机制合理的处理这个问题。Jdk内置的拒绝策略均实现了RejectedExecutionHandler
接口。
AbortPolicy
(默认)
直接抛出RejectedExecutionException
异常阻止系统正常运行。
CallerRunsPolicy
调用者运行一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新的任务流量。
DiscardPolicy
直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的解决方案。
DiscardOldestPolicy
抛弃队列中等待最久的任务,然后把当前任务假如队列中尝试再次提交当前任务。
自定义线程池
new ThreadPoolExecutor(2, 5,
1L, TimeUnit.SECONDS,
new LinkedBlockingQueue(4),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
合理分配线程池
一般根据任务类型进行区分,CPU密集型、IO密集型。假设CPU为n核。
CPU密集型
任务需要大量的运算,而且没有阻塞,CPU一直全速运行。此时需要减少线程数量, 降低线程之间切换造成的开销。
参考公式: CPU核数 + 1
例8核CPU:8 + 1 = 9
IO密集型
任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力在等待中。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就时利用被浪费掉的阻塞时间。
参考公式1:CPU核数/1-阻塞系数(阻塞系数在0.8~0.9之间)
例8核CPU:8/(1-0.9) = 80
参考公式2: CPU核数 * 2
例8核CPU:8 * 2 = 16
线程池浅析
标签:syn 并且 cut locking 介绍 schedule ima 排队 任务
原文地址:https://www.cnblogs.com/chinda/p/13202224.html