线程池的做用
- 减小资源的开销
减小了每次建立线程、销毁线程的开销。
- 提升响应速度
每次请求到来时,因为线程的建立已经完成,故能够直接执行任务,所以提升了响应速度。
- 提升线程的可管理性
线程是一种稀缺资源,若不加以限制,不只会占用大量资源,并且会影响系统的稳定性。
所以,线程池能够对线程的建立与中止、线程数量等等因素加以控制,使得线程在一种可控的范围内运行,不只能保证系统稳定运行,并且方便性能调优。
线程池的实现原理
线程池通常由两种角色构成:多个工做线程 和 一个阻塞队列。数组
- 工做线程
工做线程是一组已经处在运行中的线程,它们不断地向阻塞队列中领取任务执行。
- 阻塞队列
阻塞队列用于存储工做线程来不及处理的任务。当工做线程都在执行任务时,到来的新任务就只能暂时在阻塞队列中存储。
ThreadPoolExecutor的使用
建立线程池
经过以下代码便可建立一个线程池:框架
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, timeUnit, runnableTaskQueue, handler);
- corePoolSize:基本线程数量
它表示你但愿线程池达到的一个值。线程池会尽可能把实际线程数量保持在这个值上下。
- maximumPoolSize:最大线程数量
这是线程数量的上界。
若是实际线程数量达到这个值:
- 阻塞队列未满:任务存入阻塞队列等待执行
- 阻塞队列已满:调用饱和策略
- keepAliveTime:空闲线程的存活时间
当实际线程数量超过corePoolSize时,若线程空闲的时间超过该值,就会被中止。
PS:当任务不少,且任务执行时间很短的状况下,能够将该值调大,提升线程利用率。
- timeUnit:keepAliveTime的单位
- runnableTaskQueue:任务队列
这是一个存听任务的阻塞队列,能够有以下几种选择:
- ArrayBlockingQueue
它是一个由数组实现的阻塞队列,FIFO。
- LinkedBlockingQueue
它是一个由链表实现的阻塞队列,FIFO。
吞吐量一般要高于ArrayBlockingQueue。
fixedThreadPool使用的阻塞队列就是它。
它是一个无界队列。
- SynchronousQueue
它是一个没有存储空间的阻塞队列,任务提交给它以后必需要交给一条工做线程处理;若是当前没有空闲的工做线程,则当即建立一条新的工做线程。
cachedThreadPool用的阻塞队列就是它。
它是一个无界队列。
- PriorityBlockingQueue
它是一个优先权阻塞队列。
- handler:饱和策略
当实际线程数达到maximumPoolSize,而且阻塞队列已满时,就会调用饱和策略。
JDK1.5由四种饱和策略:
- AbortPolicy
默认。直接抛异常。
- CallerRunsPolicy
只用调用者所在的线程执行任务。
- DiscardOldestPolicy
丢弃任务队列中最久的任务。
- DiscardPolicy
丢弃当前任务。
提交任务
能够向ThreadPoolExecutor提交两种任务:Callable和Runnable。异步
- Callable
该类任务有返回结果,能够抛出异常。
经过submit函数提交,返回Future对象。
可经过get获取执行结果。
- Runnable
该类任务只执行,没法获取返回结果,并在执行过程当中没法抛异常。
经过execute提交。
关闭线程池
关闭线程池有两种方式:shutdown和shutdownNow,关闭时,会遍历全部的线程,调用它们的interrupt函数中断线程。但这两种方式对于正在执行的线程处理方式不一样。函数
- shutdown()
仅中止阻塞队列中等待的线程,那些正在执行的线程就会让他们执行结束。
- shutdownNow()
不只会中止阻塞队列中的线程,并且会中止正在执行的线程。
ThreadPoolExecutor运行机制
当有请求到来时:性能
- 若当前实际线程数量 少于 corePoolSize,即便有空闲线程,也会建立一个新的工做线程;
- 若当前实际线程数量处于corePoolSize和maximumPoolSize之间,而且阻塞队列没满,则任务将被放入阻塞队列中等待执行;
- 若当前实际线程数量 小于 maximumPoolSize,但阻塞队列已满,则直接建立新线程处理任务;
- 若当前实际线程数量已经达到maximumPoolSize,而且阻塞队列已满,则使用饱和策略。
设置合理的线程池大小
任务通常可分为:CPU密集型、IO密集型、混合型,对于不一样类型的任务须要分配不一样大小的线程池。spa
- CPU密集型任务
尽可能使用较小的线程池,通常为CPU核心数+1。
由于CPU密集型任务使得CPU使用率很高,若开过多的线程数,只能增长上下文切换的次数,所以会带来额外的开销。
- IO密集型任务
可使用稍大的线程池,通常为2*CPU核心数。
IO密集型任务CPU使用率并不高,所以可让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。
- 混合型任务
能够将任务分红IO密集型和CPU密集型任务,而后分别用不一样的线程池去处理。
只要分完以后两个任务的执行时间相差不大,那么就会比串行执行来的高效。
由于若是划分以后两个任务执行时间相差甚远,那么先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,并且还要加上任务拆分与合并的开销,得不偿失。
Executor两级调度模型

在HotSpot虚拟机中,Java中的线程将会被一一映射为操做系统的线程。
在Java虚拟机层面,用户将多个任务提交给Executor框架,Executor负责分配线程执行它们;
在操做系统层面,操做系统再将这些线程分配给处理器执行。操作系统
Executor结构

Executor框架中的全部类能够分红三类:线程
- 任务
任务有两种类型:Runnable和Callable。
- 任务执行器
Executor框架最核心的接口是Executor,它表示任务的执行器。
Executor的子接口为ExecutorService。
ExecutorService有两大实现类:ThreadPoolExecutor和ScheduledThreadPoolExecutor。
- 执行结果
Future接口表示异步的执行结果,它的实现类为FutureTask。
线程池
Executors工厂类能够建立四种类型的线程池,经过Executors.newXXX便可建立。3d
1. FixedThreadPoolcode
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}

- 它是一种固定大小的线程池;
- corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads;
- keepAliveTime为0,意味着一旦有多余的空闲线程,就会被当即中止掉;但这里keepAliveTime无效;
- 阻塞队列采用了LinkedBlockingQueue,它是一个无界队列;
- 因为阻塞队列是一个无界队列,所以永远不可能拒绝任务;
- 因为采用了无界队列,实际线程数量将永远维持在nThreads,所以maximumPoolSize和keepAliveTime将无效。
2. CachedThreadPool
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>());
}

- 它是一个能够无限扩大的线程池;
- 它比较适合处理执行时间比较小的任务;
- corePoolSize为0,maximumPoolSize为无限大,意味着线程数量能够无限大;
- keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;
- 采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必需要找到一条工做线程处理他,若是当前没有空闲的线程,那么就会再建立一条新的线程。
3. SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor(){
return new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}

- 它只会建立一条工做线程处理任务;
- 采用的阻塞队列为LinkedBlockingQueue;
4. ScheduledThreadPool
它用来处理延时任务或定时任务。

- 它接收SchduledFutureTask类型的任务,有两种提交任务的方式:
- scheduledAtFixedRate
- scheduledWithFixedDelay
- time:任务开始的时间
- sequenceNumber:任务的序号
- period:任务执行的时间间隔
- DelayQueue内部封装了一个PriorityQueue,它会根据time的前后时间排序,若time相同则根据sequenceNumber排序;
- DelayQueue也是一个无界队列;
- 工做线程会从DelayQueue取已经到期的任务去执行;
- 执行结束后从新设置任务的到期时间,再次放回DelayQueue