使⽤线程池的原因:
1、降低资源消耗
创建/销毁线程需要消耗系统资源,线程池可以复⽤已创建的线程。
2、控制并发的数量。并发数量过多,可能会导致资源消耗过多,从⽽造成服务器崩溃。
3、提高线程的可管理性
线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
4、提高响应速度
当任务到达时,任务可以不需要等到线程创建就能立即执行。
线程池的类ThreadPoolExecutor
线程池顶层接⼝是 Executor 接⼝, ThreadPoolExecutor 是这个接⼝的实现类
类ThreadPoolExecutor 的构造函数中参数
1、int corePoolSize:该线程池中核⼼线程数最⼤值
核⼼线程默认情况下会⼀直存在于线程池中,即使这个核⼼线程什么都不⼲
⾮核⼼线程如果⻓时间的闲置,就会被销毁
2、int maximumPoolSize:该线程池中线程总数最⼤值
核⼼线程数量 + ⾮核⼼线程数量
3、long keepAliveTime:⾮核⼼线程闲置超时时⻓
⾮核⼼线程如果处于闲置状态超过该值,就会被销毁
4、TimeUnit unit:keepAliveTime的单位
5、BlockingQueue workQueue:阻塞队列,维护着等待执⾏的Runnable任务对象
(1)LinkedBlockingQueue
链式阻塞队列,底层数据结构是链表,默认⼤⼩是 Integer.MAX_VALUE ,
也可以指定⼤⼩。
(2) ArrayBlockingQueue
数组阻塞队列,底层数据结构是数组,需要指定队列的⼤⼩。
(3)SynchronousQueue
同步队列,内部容量为0,每个put操作必须等待⼀个take操作,反之亦
然。
(4)DelayQueue
延迟队列,该队列中的元素只有当其指定的延迟时间到了,才能够从队列
中获取到该元素 。
6、ThreadFactory threadFactory
创建线程的⼯⼚ ,⽤于批量创建线程,统⼀在创建线程时设置⼀些参数,如是
否守护线程、线程的优先级等。如果不指定,会新建⼀个默认的线程⼯⼚。
7、RejectedExecutionHandler handler
拒绝处理策略,线程数量⼤于最⼤线程数就会采⽤拒绝处理策略
四种拒绝处理的策略为 :
(1) ThreadPoolExecutor.AbortPolicy:默认拒绝处理策略,丢弃任务并抛
出RejectedExecutionException异常。
(2)ThreadPoolExecutor.DiscardPolicy:丢弃新来的任务,但是不抛出异
常。
(3)ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列头部(最旧的)
的任务,然后重新尝试执⾏程序(如果再次失败,重复此过程)。
(4) ThreadPoolExecutor.CallerRunsPolicy:由调⽤线程处理该任务。
线程池的处理流程
1、线程总数量 < corePoolSize,⽆论线程是否空闲,都会新建⼀个核⼼线程执⾏
任务(让核⼼线程数量快速达到corePoolSize,在核⼼线程数量 <
corePoolSize时)。注意,这⼀步需要获得全局锁。
2、线程总数量 >= corePoolSize时,新来的线程任务会进⼊任务队列中等待,然
后空闲的核⼼线程会依次去缓存队列中取任务来执⾏(体现了线程复⽤)。
3、 当缓存队列满了,说明这个时候任务已经多到爆棚,需要⼀些“临时⼯”来执⾏
这些任务了。于是会创建⾮核⼼线程去执⾏这个任务。注意,这⼀步需要获得
全局锁。
4、缓存队列满了, 且总线程数达到了maximumPoolSize,则会采取上⾯提到的
拒绝策略进⾏处理
线程池是如何实现线程复用的
⾸先去执⾏创建这个worker时就有的任务,当执⾏完这个任务后,worker的⽣命周期并没有结束,在 while 循环中,worker会不断地调⽤ getTask ⽅法从阻塞队列中获取任务然后调⽤ task.run() 执⾏任务,从⽽达到复⽤线程的⽬的。只要 getTask ⽅法不返回 null ,此线程就不会退出。
常见的线程池
1、newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
当需要执⾏很多短时间的任务时,CacheThreadPool的线程复⽤率⽐较⾼, 会显著的提⾼性能。⽽且线程60s后会回收,意味着即使没有任务进来,
CacheThreadPool并不会占⽤很多资源。
2、newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads,
nThreads,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
核⼼线程数量和总线程数量相等,都是传⼊的参数nThreads,所以只能创建核⼼线
程,不能创建⾮核⼼线程。因为LinkedBlockingQueue的默认⼤⼩是
Integer.MAX_VALUE,故如果核⼼线程空闲,则交给核⼼线程处理;如果核⼼线程
不空闲,则⼊列等待,直到核⼼线程空闲。
newCachedThreadPool和newFixedThreadPool的区别
(1)因为 corePoolSize == maximumPoolSize ,所以FixedThreadPool只会创建核
⼼线程。 ⽽CachedThreadPool因为corePoolSize=0,所以只会创建⾮核⼼线
程。
(2)在 getTask() ⽅法,如果队列⾥没有任务可取,线程会⼀直阻塞在
LinkedBlockingQueue.take() ,线程不会被回收。 CachedThreadPool会在
(3)由于线程不会被回收,会⼀直卡在阻塞,所以没有任务的情况下,
FixedThreadPool占⽤资源更多。
(4)都⼏乎不会触发拒绝策略,但是原理不同。FixedThreadPool是因为阻塞队列
可以很⼤(最⼤为Integer最⼤值),故⼏乎不会触发拒绝策略;
CachedThreadPool是因为线程池很⼤(最⼤为Integer最⼤值),⼏乎不会导
致线程数量⼤于最⼤线程数,故⼏乎不会触发拒绝策略。
60s后收回。
3、newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1,
1,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
有且仅有⼀个核⼼线程( corePoolSize == maximumPoolSize=1),使⽤了
LinkedBlockingQueue(容量很⼤),所以,不会创建⾮核⼼线程。所有任务按照
先来先执⾏的顺序执⾏。如果这个唯⼀的线程不空闲,那么新来的任务会存储在任
务队列⾥等待执⾏。
4、newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize,
Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS,
MILLISECONDS,
new DelayedWorkQueue());
}
创建⼀个定⻓线程池,⽀持定时及周期性任务执⾏。