1. 为什么要有线程池?
线程池能够对线程进行统一分配,调优和监控
- 减低资源消耗(线程无限制的创建,然后使用完毕后销毁)
- 提高响应速度(无需创建线程)
- 提高线程的可管理性
2. Execute原理
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
-
corePoolSize
:核心线程数量 -
maximumPoolSize
:最大线程数量 -
keepAliveTime
: 线程存活时间(线程空闲的存活时间,默认情况下,该参数只在线程数大于corePoolSize时才有用, 超过这个时间的空闲线程将被终止) -
unit
: 时间单位 -
workQueue
: 工作队列 -
handler
: 拒绝策略
当一个任务提交至线程池之后:
- 判断当前运行的线程数量是否少于corePoolSize(核心线程数量),如果少于核心线程数(即使有其他的空闲线程能够执行新来的任务),那么就会创建一个新的工作线程执行任务,如果已经达到了核心线程数,那么进入第二步。
- 超过了核心线程数后,判断队列是否已满,如果没有满那么后面进来的线程会被放入队列中,等待核心线程执行完任务,在从队列中取出任务执行。如果队列满了,那就进入第三步。
- 判断线程池中的线程数是否超过了最大线程数
,如果没有那么就会创建一个临时线程用来执行任务,如果超过了,那么就会进入第四步。 - 在线程池中配置了那种拒绝策略,根据不同的拒绝策略决定如何处理任务。
3. 有哪几种工作队列?
workQueue用来保存等待被执行的任务的阻塞队列,JDK提供了以下队列:
-
ArrayBlockingQueue
:基于数组结构的有界阻塞队列,按FIFO排序任务;当正在执行的线程数等于核心线程数时,多余的任务被缓存在队列中等待有空闲的线程时继续执行,当队列已满时,加入队列失败,会开启新的线程去执行,当达到了最大线程数,再有元素加入时就会报错。 -
LinkedBlockingQueue
: 基于链表结构的有界缓存等待队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQueue。此队列的默认和最大长度为Integer.MAX_VALUE。 -
SynchronousQuene
: 一个不存储元素的无缓冲等待队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;使用该队列一般要求最大核心线程数是无界的,避免线程拒绝执行操作。 -
PriorityBlockingQuene
: 具有优先级的无界阻塞队列。默认情况下元素采取自然顺序升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则,或者初始化PriorityBlockingQueue时,指定构造参数Comparator来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。
4. 拒绝策略有哪些?
线程池的拒绝策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
-
AbortPolicy
:直接抛出异常,默认策略; -
CallerRunsPolicy
:用调用者所在的线程来执行任务; -
DiscardOldestPolicy
:丢弃阻塞队列中靠最前的任务,并执行当前任务; -
DiscardPolicy
:直接丢弃后进入队列的任务;
5. 使用工具类创建线程池的三种类型?
- newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
线程池的线程数量达corePoolSize后,即使线程池没有可执行任务时,也不会释放线程。
FixedThreadPool的工作队列为无界队列LinkedBlockingQueue(队列容量为Integer.MAX_VALUE), 这会导致以下问题:
- 线程池里的线程数量不超过corePoolSize,这导致了maximumPoolSize和keepAliveTime将会是个无用参数
- 由于使用了无界队列, 所以FixedThreadPool永远不会拒绝, 即饱和策略失效
- newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
初始化的线程池中只有一个线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行.
由于使用了无界队列, 所以SingleThreadPool永远不会拒绝, 即饱和策略失效。
- newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列; 和newFixedThreadPool创建的线程池不同,newCachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销; 执行过程与前两种稍微不同:
- 主线程调用SynchronousQueue的offer()方法放入task, 倘若此时线程池中有空闲的线程尝试读取 SynchronousQueue的task, 即调用了SynchronousQueue的poll(), 那么主线程将该task交给空闲线程. 否则执行(2)
- 当线程池为空或者没有空闲的线程, 则创建新的线程执行任务.
- 执行完任务的线程倘若在60s内仍空闲, 则会被终止. 因此长时间空闲的CachedThreadPool不会持有任何线程资源
网友评论