线程池解决了两个问题:
- 由于减少了每个任务的调用开销,它们通常在执行大量异步任务时提供更高的性能
- 它们提供了一种约束和管理执行大量任务时消耗的资源(包括线程)的方法。 每个
ThreadPoolExecutor
还维护一些基本的统计数据,比如已完成任务的数量。
ThreadPoolExecutor
类的构造函数如下:
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
-
corePoolSize:
核心池的大小,在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()
或者prestartCoreThread()
方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize
个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中线程数小于corePoolSize时,即使有空闲线程,仍然会创建新线程执行;当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中; -
maximumPoolSize
线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。 -
keepAliveTime:
表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize
时,keepAliveTime
才会起作用,直到线程池中的线程数不大于corePoolSize
,即当线程池中的线程数大于corePoolSize
时,如果一个线程空闲的时间达到keepAliveTime
,则会终止,直到线程池中的线程数不超过corePoolSize
。但是如果调用了allowCoreThreadTimeOut()
方法,在线程池中的线程数不大于corePoolSize
时,keepAliveTime
参数也会起作用,只要keepAliveTime
值非零。 -
workQueue:
任何BlockingQueue
都可用于传输和保存提交的任务。 此队列的使用与线程池大小互相影响:- 如果运行的线程少于
corePoolSize
,Executor
总是倾向于添加一个新线程,而不是排队。 - 如果
corePoolSize
或更多线程正在运行,Executor
总是倾向于将请求排队,而不是添加新线程。 - 如果请求不能排队,则创建一个新线程,如果线程数超过
maximumPoolSize
,任务将被拒绝。
一般有三种排队策略:
- 如果运行的线程少于
-
Direct handoffs
(直接交接)。 一个很好的默认选择是SynchronousQueue
,它将任务移交给线程,而不以其他方式保留任务。 在这里,如果没有线程可以立即运行任务,将构建一个新线程。 在处理可能具有内部依赖性的请求集时,此策略避免锁定。 直接切换通常需要无限制的maximumPoolSizes
,以避免拒绝新提交的任务。 所以,当命令继续以比处理它们更快的速度到达时,可能造成无限制的线程增长。 -
Unbounded queues
(无界队列)。 使用Unbounded queues
(例如没有预定义容量的 LinkedBlockingQueue)将导致在所有corePoolSize
线程忙时在队列中等待新任务。 因此,创建的 corePoolSize 线程不会超过个。 (因此 maximumPoolSize 的值没有任何影响。) 当每个任务完全独立于其他任务时,这可能是适当的,因此任务不会影响彼此的执行; 例如,在网页服务器中。 虽然这种排队方式可以有效地消除请求的短暂爆发,但它也承认,当命令持续以平均快于处理速度到达时,工作队列可能会出现无限增长 -
Bounded queues
有界队列。 有界队列(例如,ArrayBlockingQueu
e)在使用有限的maximumPoolSizes
时有助于防止资源耗尽,但是调优和控制可能更加困难。 队列大小和最大池大小可能相互抵消: 使用大队列和小池可以最大限度地减少 CPU 使用、操作系统资源和上下文切换开销,但可能导致人为的低吞吐量。 如果任务经常阻塞(例如,如果它们是 i / o 绑定的) ,系统可能会为更多的线程安排时间。 使用小队列通常需要更大的池大小,这使 cpu 更加繁忙,但可能会遇到不可接受的调度开销,这也降低了吞吐量
总结:
if (wc + 1< coreSize`)// 如果线程正在运行的线程数 wc+1 小于 coreSize,则新建一个线程。
else //如果大于 coreSize
if (wc + 1 < workQueue.size()) 如果队列没满,有限加入队列。
else
if (wc + 1 - workQueue.size() > maximumPoolSize)
如果需要的线程数没有超过最大线程数,则创建线程
else
线程池拒绝错误。
-
ThreadFactory:
线程工厂,用来创建线程 -
RejectedExecutionHandler:
当Executor
关闭执行execute(Runnable)
中提交的新任务被拒绝时,以及 Executor 为最大线程和工作队列容量使用有限的边界时,并且已经饱和时被拒绝。 在这两种情况下,execute 方法都会调用其RejectedExecutionHandler.rejectedExecution (Runnable,ThreadPoolExecutor)
。 提供了四个预定义的处理程序策略:
- 默认的
ThreadPoolExecutor.AbortPolicy
,处理程序在拒绝时抛出运行时异常RejectedExecutionException
。 -
ThreadPoolExecutor.CallerRunsPolicy
由调用线程本身运行这个任务。 这提供了一个简单的反馈控制机制,可以降低新任务提交的速度。 -
ThreadPoolExecutor.DiscardPolicy
:也是丢弃任务,但是不抛出异常。 -
ThreadPoolExecutor.DiscardOldestPolicy
:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
如何设置参数
如何来设置
需要根据几个值来决定
tasks :每秒的任务数,假设为500~1000
taskcost:每个任务花费时间,假设为0.1s
responsetime:系统允许容忍的最大响应时间,假设为1s
做几个计算
corePoolSize = 每秒需要多少个线程处理?
threadcount = tasks/(1/taskcost) =taskstaskcout = (500~1000)0.1 = 50~100 个线程。corePoolSize设置应该大于50
根据8020原则,如果80%的每秒任务数小于800,那么corePoolSize设置为80即可
queueCapacity = (coreSizePool/taskcost)responsetime
计算可得 queueCapacity = 80/0.11 = 80。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行
切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)
计算可得 maxPoolSize = (1000-80)/10 = 92
(最大任务数-队列容量)/每个线程每秒处理能力 = 最大线程数
rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理
keepAliveTime和allowCoreThreadTimeout采用默认通常能满足