自定义线程池(一)

在我们自定义线程池之前,先预备一点前置知识

  • 为什么要使用线程池?
  1. New Thread这种方式性能差
  2. 缺乏统一管理,循环创建会照成OOM
  3. 功能单一

对应的就是线程池的优点,JAVA1.5出来的Concurrent包里面提供了四种创建线程池的方法,我们可以直接创建,但阿里Java开发手册有这么一条

【强制】线程池不允许使用 Executors 去创建,而是通过ThreadPoolExecutor 的方式

这样的好处就强制开发者去了解线程池的创建原理,从而避免资源耗尽的风险,而且Executors中四种线程池创建方式其实都依赖于ThreadPollExecutor这个类的构造。

  • 线程池概念

从图中可以看到各个类和接口的关系,同时可以看到Executors和其他类或接口没有什么直接关系,我们可以把他当成一个线程池工厂,用来创建四种线程池。

先说下ThreadPoolExecutor

1
2
3
4
5
6
7
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

corePoolSize:核心线程数大小

maximumPoolSize:线程池最大线程数

keepAliveTime:当线程数大于核心时,清除空闲keepAliveTime时间的线程

unit:时间单位

workQueue:存储等待任务的队列

threadFactory:线程工厂

handler:拒绝策略

刚创建的线程池是没有任何线程的,当过来一个任务,就启动一个线程去执行,再过来一个还创建一个新的线程,即便之前的已经空闲了,当线程池达到核心线程数的话,如果没有空闲线程,就把新加进来的任务放到阻塞队列,如果阻塞队列是有边界的话,到达边界以后就会去创建新的线程,接下来就会出现两种情况:

  1. 如果任务不再来了,那么就会把空闲keepAliveTime时间的线程给移除掉
  2. 如果任务接着来,并且已经达到了最大线程数,那么就要执行拒绝策略了

其中等待队列有多种ArrayBlockingQueue,LinkedBlockingQueue,LinkedBlockingDeque,SynchronousQueue,根据具体情况而定。

拒绝策略也有很多种

  1. AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 (默认)
  2. DiscardPolicy:也是丢弃任务,但是不抛出异常。
  3. DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
  4. CallerRunsPolicy:由调用线程处理该任务
  • 四种创建线程池的方法
1
2
3
4
5
6
7
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(
1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

创建一个单线程,你看名字里面都没有ThreadPool:smile:,如果这个线程死了,他会创建一个新的线程来替代。

1
2
3
4
5
6
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(
0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

这个线程的特点从等待队列就可以看出来,这个队列根本不存储数据,只是做一个通道,一边进一边出。最大线程数为Integer.MAX_VALUE,也就是过来一个任务我就创建一个线程,当线程数大于任务数的时候就去回收那些空闲超过60秒的线程。

1
2
3
4
5
6
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

这个线程池核心线程和最大线程数相等,也就是说,过来一个任务,就直接创建一个线程,等到了最大线程数,你看keepAliveTime为0,所以就是不管线程使用不适用都不回收,如果线程挂了的话会重新创建一个。为了不然频繁丢弃任务,这里使用了一个无界链表队列。

1
2
3
4
5
6
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(
corePoolSize, Integer.MAX_VALUE,
0, NANOSECONDS,
new DelayedWorkQueue());
}

这个线程池的特点从名字就可以看出来,定期线程池,也就是任务会定期执行,这个定期是在他调用schedule方法的时候,就像一个Timer定时器一样,而他的实现主要是靠DelayedWorkQueue这个延迟队列,他规定只有到达规定延迟时间以后才能获取里面的元素