在我们自定义线程池之前,先预备一点前置知识
- 为什么要使用线程池?
- New Thread这种方式性能差
- 缺乏统一管理,循环创建会照成OOM
- 功能单一
对应的就是线程池的优点,JAVA1.5出来的Concurrent包里面提供了四种创建线程池的方法,我们可以直接创建,但阿里Java开发手册有这么一条
【强制】线程池不允许使用 Executors 去创建,而是通过ThreadPoolExecutor 的方式
这样的好处就强制开发者去了解线程池的创建原理,从而避免资源耗尽的风险,而且Executors中四种线程池创建方式其实都依赖于ThreadPollExecutor这个类的构造。
- 线程池概念
/1.png)
从图中可以看到各个类和接口的关系,同时可以看到Executors和其他类或接口没有什么直接关系,我们可以把他当成一个线程池工厂,用来创建四种线程池。
先说下ThreadPoolExecutor
1 | public ThreadPoolExecutor(int corePoolSize, |
corePoolSize:核心线程数大小
maximumPoolSize:线程池最大线程数
keepAliveTime:当线程数大于核心时,清除空闲keepAliveTime时间的线程
unit:时间单位
workQueue:存储等待任务的队列
threadFactory:线程工厂
handler:拒绝策略
刚创建的线程池是没有任何线程的,当过来一个任务,就启动一个线程去执行,再过来一个还创建一个新的线程,即便之前的已经空闲了,当线程池达到核心线程数的话,如果没有空闲线程,就把新加进来的任务放到阻塞队列,如果阻塞队列是有边界的话,到达边界以后就会去创建新的线程,接下来就会出现两种情况:
- 如果任务不再来了,那么就会把空闲keepAliveTime时间的线程给移除掉
- 如果任务接着来,并且已经达到了最大线程数,那么就要执行拒绝策略了
其中等待队列有多种ArrayBlockingQueue,LinkedBlockingQueue,LinkedBlockingDeque,SynchronousQueue,根据具体情况而定。
拒绝策略也有很多种
- AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 (默认)
- DiscardPolicy:也是丢弃任务,但是不抛出异常。
- DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- CallerRunsPolicy:由调用线程处理该任务
- 四种创建线程池的方法
1 | public static ExecutorService newSingleThreadExecutor() { |
创建一个单线程,你看名字里面都没有ThreadPool:smile:,如果这个线程死了,他会创建一个新的线程来替代。
1 | public static ExecutorService newCachedThreadPool() { |
这个线程的特点从等待队列就可以看出来,这个队列根本不存储数据,只是做一个通道,一边进一边出。最大线程数为Integer.MAX_VALUE,也就是过来一个任务我就创建一个线程,当线程数大于任务数的时候就去回收那些空闲超过60秒的线程。
1 | public static ExecutorService newFixedThreadPool(int nThreads) { |
这个线程池核心线程和最大线程数相等,也就是说,过来一个任务,就直接创建一个线程,等到了最大线程数,你看keepAliveTime为0,所以就是不管线程使用不适用都不回收,如果线程挂了的话会重新创建一个。为了不然频繁丢弃任务,这里使用了一个无界链表队列。
1 | public ScheduledThreadPoolExecutor(int corePoolSize) { |
这个线程池的特点从名字就可以看出来,定期线程池,也就是任务会定期执行,这个定期是在他调用schedule方法的时候,就像一个Timer定时器一样,而他的实现主要是靠DelayedWorkQueue这个延迟队列,他规定只有到达规定延迟时间以后才能获取里面的元素