线程池好处
- 重用存在的线程,减少对象创建,销毁的开销
- 可有效控制最大并发线程数
- 提供定时执行,定期执行等功能.
ThreadPoolExecutor
线程池可以通过ThreadPoolExecutor生成,以下是它的构造参数:
public TheadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
几个核心参数的作用:
- corePoolSize: 核心线程数量
- maximumPoolSize: 最大线程数
- workQueue: 任务队列,存储等待执行的任务(有界阻塞)
- keepAliveTime: 线程没有任务执行时最多保持多久时间
- unit: 上面的时间单位
- threadFactory: 线程工厂,用于创建线程
- rejectHandler: 当队列已满时如何处理任务的策略
任务执行
线程池执行过程,亦即execute()方法的调用过程:
- 提交一个任务,若线程池里的线程数小于corePoolSize时,创建一个新线程去处理提交任务.
- 如果线程数已经大于等于corePoolSize,新提交的任务会被放入任务队列workQueue排队等待执行.
- 如果线程数已经大于等于corePoolSize,并且任务队列也已经满了,会判断线程数是否达到maximumPoolSize,若未达到,则创建一个非核心线程来执行提交的任务.
- 如果线程数已经达到maximumPoolSize,新任务到来之时会采用拒绝策略处理.
四种拒绝策略
- AbortPolicy(默认,抛出一个异常)
- DiscardPolicy(直接丢弃新任务)
- DiscardOldestPolicy(丢弃队列里最老的任务,将当前任务继续提交给线程池)
- CallerRunsPolicy(交给线程池调用所在的线程进行处理,即此任务会从异步执行变为同步执行)
线程池异常处理
-
异常需要主动在任务中用try catch捕获,不会抛出到主线程.
-
如果调用的是submit()方法,异常也可以在FutureTask的get()方法处抛出.
-
也可以为工作线程设置UncaughtExceptionHandler来处理异常.
ExecutorService threadPool = Executors.newFixedThreadPool(1, r -> {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(
(t1, e) -> {
System.out.println(t1.getName() + "线程抛出的异常"+e);
});
return t;
});
threadPool.execute(()->{
Object object = null;
System.out.print("result## " + object.toString());
});
工作队列
种类有:
- ArrayBlockingQueue
- LinkedBlockingQueue
- DelayQueue
- PriorityBlockingQueue
- SynchronousQueue
ArrayBlockingQueue: 用数组实现的有界阻塞队列,FIFO
LinkedBlockingQueue: 用链表实现的阻塞队列,FIFO,容量可以进行设置,若不设置的话默认无边界.newFixedThreadPool,newSingleThreadExecutor线程池使用了这个队列
DelayedWorkQueue: 一个任务定期执行的延迟队列,根据指定的执行时间从小到大排序,否则根据入队的先后排序.newScheduledThreadPool线程池使用了这个队列
PriorityBlockingQueue: 具有优先级的无界阻塞队列
SynchronousQueue: 一个不存储元素的阻塞队列,只负责移交数据,每个插入操作必须等待另一个线程取出,否则一直处于阻塞状态.newCachedThreadPool线程池使用了这个队列。
Executors创建的几种常用的线程池
-
newFixedThreadPool (固定数目线程的线程池) :
-
newCachedThreadPool(线程无限的线程池)
-
newSingleThreadExecutor(单线程的线程池)
-
newScheduledThreadPool(定时或周期执行的线程池)
newFixedThreadPool特点 :
- 核心线程数和最大线程数大小一样(数量由用户指定,必须)
- 没有所谓的非空闲时间,即keepAliveTime为0
- 阻塞队列为无界队列LinkedBlockingQueue
- fixed的意思即队列大小适应任务数量,来多少处理多少
- 不会丢弃任务,但是任务积压过多会导致内存溢出.
- 适用于CPU密集任务,不适用IO密集任务
newCachedThreadPool特点:
- 核心线程数为0,即所有线程都是非核心线程
- 最大线程数为Integer.MAX_VALUE
- 阻塞队列是SynchronousQueue
- 非核心线程空闲存活为60s
- 适用并发执行大量短期的小任务
newSingleThreadExecutor特点:
- 核心线程和最大线程都是1
- 阻塞队列是LinkedBlockingQueue
- keepAliveTime为0
- 适用于串行执行任务
- 串行任务的数量一般不是很大,因此不用担心内存爆炸的问题
newScheduledThreadPool特点:
- 最大线程数是Integer.MAX_VALUE
- 阻塞队列是DelayedWorkQueue
- keepAliveTime为0
- 可选延迟策略有两种:
- scheduleAtFixedRate() :按某种速率周期执行
- scheduleWithFixedDelay():在某个延迟后执行
线程池状态
- running: 运行态
- shutdown: 调用shutdown()方法后进入此阶段,不再处理新任务,任务队列的任务处理完后会进入tidying
- stop: 调用shutdownNow()方法后进入此阶段,新任务与任务队列的任务都不处理,当各线程完成后进入tidying
- tidying: 该状态表明所有任务都运行完毕,可以准备进行销毁
- terminated: tidying阶段调用terminated()后进入此阶段(该方法什么也不做),表示线程池彻底终止.
一些思考题
-
为什么newCachedThreadPool要使用SynchronousQueue队列?
原因有两个:
- SynchronousQueue结构简单,消费速度比较快,高并发下效率更高些.
- SynchronousQueue适用于生产与消费速率一致的情况,而当队列中有新任务之后,newCachedThreadPool能马上新建线程去消费任务,因此能发挥SynchronousQueue的高性能优势