设计一个线程池
首先,我们要设计线程池的目的是为了管理线程,所以它的核心就是提供线程来处理我们提交的任务。在设计之前我们先来思考一下几个问题:
1、初始化的时候就创建线程还是提交任务的时候创建线程?
2、假设我们提交的任务数大于了最大线程数怎么处理?直接抛弃?还是存放到队列里?
3、这个队列有没有上限?如果存放到队列里队列达到了上限怎么办?
1)直接抛弃?
2)抛异常不让客户端提交?
3)阻塞,等队列有空位再存这个任务?
4)阻塞一段时间,时间过了队列还没有空位存就放弃提交任务?
1、我们当然希望有任务才去创建线程,因为如果这个线程池没有提交任务就创建了线程,那么这些线程就会空转或者睡眠,造成浪费。
2、当客户端提交的任务大于线程池的最大线程数maximumPoolSize时,我们也不应该把这个任务直接丢弃,因为线程池中的线程可能很快就把当前任务执行完。所以比较好的方式就是用个队列存储起来,当线程执行完了可以立马从线程池里面拿到任务执行。其实为了设计得更加合理一点应该设置一个核心线程数corePoolSize。
1)当工作线程数wc < 核心线程数corePoolSize时,直接创建新的工作线程
2)当工作线程数wc > = 核心线程数corePoolSize时,把任务存放到队列中。如果此时工作线程为0,我们应该开启空闲线程去处理我们提交的任务(核心线程数为0),不然我们的任务就只是存在队列中而没有被执行。所以我们还需要看看工作线程是否为0,如果为0则应该创建线程去处理这些任务。
3)当队列满了的时候 & 当工作线程数wc <= 最大线程数maximumPoolSize,创建新的工作线程线程(空闲线程)
4)当队列满了的时候 & 当工作线程数wc > 最大线程数maximumPoolSize,执行拒绝策略
3、一般来说我们的线程池的任务队列应该也要有个最大值,不能一直存,不然会有内存溢出的风险。但也不能排除有些场景需要用到无界队列,所以我们的队列也作一个简单的抽象,分为有界和无界队列。这里我们就只实现一个有界队列,有界队列实现了再看无界队列就很简单了。当这个队列满了的话,到底要不要放弃这个任务或者以什么方式放弃这个任务都应该视应用场景来划分,所以不同的场景会有不同的策略,我们这里可以使用策略模式对不同的场景设计不同的策略。
通过分析以上的三个问题,我们可以得到一个线程池的大概工作流程图:
接下来分析一下我们线程池的一个组成部分:workers(工作线程集合)、队列、拒绝策略以及一个线程工厂,还有核心线程数和最大线程数。除此之外,我们应该还要设计一个线程睡眠等待的最大时间,如果超过了这个时间还没有任务就把线程回收掉。然后我们的这个线程池还要有个核心的api就是submit来提交我们的任务。现在我们来设计一下我们这个线程池的类图(图可能画得不太好看,将就着看吧):
设计代码
这里我也模仿一下jdk的源码设计两个接口:Executor和ThreadPool(ThreadPool继承Executor),Executor提供一个execute的api执行任务,ThreadPool则提供submit和processWorkerExit两个api用来提交任务和释放线程,这两个接口的代码如下:
public interface Executor {
/**
* 执行具体的task
*/
void excute(Runnable task);
}
public interface ThreadPool extends Executor {
/**
* 处理提交的任务
*
* @param task
*/
void submit(Runnable task);