为什么要使用线程池?
首先我们想一想为什么我们要使用线程池呢?这里主要我们可以给出以下原因:
- 如果创建线程的频率很高,每次处理的时间会很短,这时我们频繁的创建和销毁线程会造成巨大的开销。
- 其次如果每次需要一个线程就创建一个线程,我们管理线程将会很不方便。
上面就是我们在开发时需要考虑的是否要使用线程池的因素。
Java中提供的线程池
Java中可以使用Executors提供的静态工厂方法创建了四种线程池:
Executors.newCachedThreadPool() 创建一个无大小限制的线程池,只要需要创建新
的线程则立即创建。每个线程如果在60秒内没有处理的则会销毁。
Executors.newFixedThreadPool() 创建一个固定大小的线程池。
Executors.newScheduledThreadPool() 创建一个延期执行的线程池。
Executors.newSingleThreadExecutor() 创建一个只有一个线程的线程池。
这四个静态工厂方法的实现如下:
newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newFixedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newScheduledThreadPool()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
new ScheduledThreadPoolExecutor(corePoolSize)的实现如下:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
从上面的实现我们可以发现
- newCachedThreadPool()和newFixedThreadPool()直接创建了一个ThreadPoolExecutor对象,只是参数不同。
- 而newSingleThreadExecutor()则是新建一个ThreadPoolExecutor对象并作为参数传递给FinalizableDelegatedExecutorService,FinalizableDelegatedExecutorService是作为一个代理类,最终的操作都是通过ThreadPoolExecutor来执行,你可以参考关于SingleThreadExecutor以及FinalizableDelegatedExecutorService这篇文章理解FinalizableDelegatedExecutorService的作用。
- newScheduledThreadPool()则是创建一个ScheduledThreadPoolExecutor对象,ScheduledThreadPoolExecutor继承ThreadPoolExecutor并实现了接口ScheduledExecutorService。ScheduledExecutorService接口定义了提交延迟反诬的四个方法。下面是ScheduledThreadPoolExecutor的继承结构图:
通过上面的分析我们明白了Java中四种线程池的核心都是ThreadPoolExecutor。下面来看一下ThreadPoolExecutor()的构造函数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
参数的意义:
corePoolSize:线程池中线程的数量,这些线程就算处于空闲状态也不会回收
maximumPoolSize:线程池中允许的线程的最大数量
keepAliveTime:超过corePoolSize数量的线程的最大存活时间
unit:KeepAliveTime的时间单位
workQueue:用于存放execute方法提交的任务
factory:用于创建线程的工程,通常可以设置线程的名字等一些属性方便调试
handler:当线程池已经关闭或者线程数量已经饱和且任务队列也饱和了
观察上面的各项参数并且结合静态工厂方法的实现,可以确定各种线程池最本质的区别就在于这几项参数值得不同。
线程池的执行流程
除了ScheduledThreadPoolExecutor之外,另外的三种线程池的submit方法的执行逻辑都是一样的,如下图:
执行逻辑简单描述如下:
- 执行submit方法提交任务。
- 首先检查当前线程数是否小于corePoolSize,小于则添加新的worker并将任务交给新建的worker运行。
- 将任务添加到任务队列中。
- worker会循环获取任务并执行。
ScheduledThreadPoolExecutor不同的是先添加任务,然后根据当前线程数量的大小决定是否创建新的Worker。
需要说明的是,Worker是一个实现了Runnable接口的类,每次创建新的worker都是创建一个Thread对象并将worker对象作为参数传递给Thread对象。之后调用Thread对象的start方法会执行Worker的run方法。Worker的run方法会执行Task的run方法。
拒绝策略
线程池中总共涉及到四种拒绝策略,它们都定义在ThreadPoolExecutor中,分别是:
CallerRunsPolicy 在当前线程直接执行任务,即直接调用task.run()
AbortPolicy 抛出运行时异常
DiscardPolicy 放弃当前任务,不做任何事
DiscardOldestPolicy 移除队首的任务,并且重新执行任务
当出现线程池已经关闭或者线程池和任务队列已经饱满时,最终都会调用到这些拒绝策略的rejectedExecution方法。
阻塞队列
Java线程池总共涉及到了三种阻塞队列,它们分别是SynchronousQueue,LinkedBlockingQueue,DelayedWorkQueue。下面简单介绍一下这几种队列的特点。
SynchronousQueue:这是一个不存储任何元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。
LinkedBlockingQueue:这是一个有界阻塞队列。此队列的默认和最大长度为Integer.MAX_VALUE。
DelayedWorkQueue:DelayedWorkQueue是ScheduledThreadPoolExecutor的内部类。
它使用数组实现存放任务,是没有上届的,可以不断放入数据,并且它使用延迟时间进行排序,
延迟时间少得排在前面。
DelayedWorkQueue可以参考Java优先级队列DelayedWorkQueue原理分析
未完待续
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。