一、为什么要使用线程池
不使用线程池的坏处:
- 频繁的线程创建和销毁会占用更多的CPU和内存
- 频繁的线程创建和销毁会对GC产生比较大的压力
- 线程太多,线程切换带来的开销将不可忽视
- 线程太少,多核CPU得不到充分利用,是一种浪费
使用线程池的优点:
- 降低资源的消耗。线程本身是一种资源,创建和销毁线程会有CPU开销;创建的线程也会占用一定的内存。
- 提高任务执行的响应速度。任务执行时,可以不必等到线程创建完之后再执行。
- 提高线程的可管理性。线程不能无限制地创建,需要进行统一的分配、调优和监控。
二、如何创建线程池
(一)线程池的实现原理
通过上图,可以知道线程池大致的流程:
- 任务提交后,判断核心线程是否已满,如果不是,则创建线程执行任务
- 如果核心满了,判断队列是否已满,如果没满,任务放在队列中等待执行
- 如果队列满了,判断线程池是否已满,如果没满,创建其他线程执行任务,缓解压力
- 如果线程池也满了,任务没地方放,也无法执行,则根据Handler策略进行处理
(二)如何创建线程池
1、通过ThreadPoolExecutor创建自定义线程池
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
参数含义:
- corePoolSize:核心线程数,必须有
- maximumPoolSize:最大线程数,必须有
- keepAliveTime:线程存活时间,当线程数超过核心线程数时,这些线程的存活时间,就是这些线程多久被销毁,必须有
- unit:存活时间单位,可以是毫秒、秒、分钟、小时和天,等等,必须有
- workQueue:等待队列,线程池中的线程数超过核心线程数时,任务将放在等待队列,它是一个BlockingQueue类型的对象,必须有
- threadFactory:线程工厂,用于创建线程,可有可无
- handler:线程池满后执行的策略,可有可无
(1)等待队列-workQueue
等待队列是BlockingQueue类型的,理论上只要是它的子类,我们都可以用来作为等待队列。
- ArrayBlockingQueue:基于数组实现的队列,数组要指定长度,所以是有界队列
- LinkedBlockingQueue:基于链表实现的队列,使用链表存储数据,默认是一个无界队列;也可以通过构造方法中的capacity设置最大元素数量,所以也可以作为有界队列
- SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作将一直处于阻塞状态。该队列也是Executors.newCachedThreadPool()的默认队列
- PriorityBlockingQueue:基于优先级别的阻塞队列,底层基于数组实现,是一个无界队列
- 延迟队列:其中的元素只有到了其指定的延迟时间,才能够从队列中出队
(2)拒绝策略-handler
所谓拒绝策略,就是当线程池满了、队列也满了的时候,我们对任务采取的措施。或者丢弃、或者执行、或者其他行为。
JDK常见的4种策略:
- CallerRunsPolicy:在调用者线程执行
- AbortPolicy:直接抛出RejectedExecutionException异常
- DiscardPolicy:直接丢弃,不做任何处理
- DiscardOldestPolicy:丢弃任务里面最先创建的那个任务,再执行当前任务
这四种策略各有优劣,比较常用的是DiscardPolicy,但是这种策略有一个弊端就是任务执行的轨迹不会被记录下来。所以,我们往往需要实现自定义的拒绝策略, 通过实现RejectedExecutionHandler接口的方式。
2、通过Executors直接创建固定线程池
Executors是一个线程工厂,可以固定创建以下线程池
// 创建单一线程的线程池
public static ExecutorService newSingleThreadExecutor();
// 创建固定数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads);
// 创建带缓存的线程池
public static ExecutorService newCachedThreadPool();
// 创建定时调度的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
// 创建流式(fork-join)线程池
public static ExecutorService newWorkStealingPool();