今天回忆一下线程池的前世今生
内容有一下几点:
- 为什么要用线程池
- 线程池的核心类 ThreadPoolExecutor
- Executors 类提供的几种常见的线程池
开始正事了
一、为什么要用线程池
1、创建和销毁线程是有消耗的,如果 创建线程的时间t1、销毁线的时间t2、线程执行的时间t3他们之间的关系是t1+t2>t3,我们就认为他们是不划算的。这样的问题可以用线程池完美解决,缓存一些线程到线程池,使用的时候从线程池中取,使用完毕就释放会线程池。这就是线程池的思想,很简单吧。
2、当线程过多是后,他们会抢占资源,会出现诡异而不可预测的事情(阻塞等),使用线程池可以避免这样的情况发生,同时可以方便的管理线程(延时执行,定时循环执行)。
二、ThreadPoolExecutor 的前世今生
1、ThreadPoolExecutor 主要有四个构造方法如下
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
参数有点多,不要怕,下面一一解释一下这些参数的含义。
1、 int corePoolSize: 该线程池最大核心线程数量。
核心线程:新建线程的时候,如果线程数量小于核心线程最大数量的时候,新建的是核心线程数量,如果大于和弦线程最大数量的时候,新建的就是非核心线程数量。
核心线程默认会一直存活下去,哪怕是这个线程闲置下来什么也不干。
如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉
简单的就不多做解释了,继续往下看吧。
2、int maximumPoolSize:该线程池中允许最大的线程数。
最大核心线程数量+非核心数量=最大的线程数
小学的数学算数,相信你能算对。
3、long keepAliveTime :存活时间
非核心线程闲置下来,经过keepAliveTime 这个时间就会被销毁。
如果ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么这个也将作用于核心线程。
4、TimeUnit unit : 时间单位
java中通用的时间单位如下:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小时
DAYS : 天
5、BlockingQueue workQueue :阻塞队里,这个大有名堂,好好说一下。
队列有好些中,下面是详解
SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize。
ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
6、ThreadFactory threadFactory:创建线程工厂
这是一个接口,源码如下:
public interface ThreadFactory {
/**
* Constructs a new {@code Thread}. Implementations may also initialize
* priority, name, daemon status, {@code ThreadGroup}, etc.
*
* @param r a runnable to be executed by new thread instance
* @return constructed thread, or {@code null} if the request to
* create a thread is rejected
*/
Thread newThread(Runnable r);
}
子类实现这个方法,具体实现新建的线程的过程,认为麻烦的可以略过,有默认的。
7、RejectedExecutionHandler handler:异常处理的handler
这个是专门处理异常的,上面说到的抛出异常都是这里处理的,有默认的。
参数讲完了,是不是soso easy。
还有一个方法:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
这个方法就是添加线程的方法。
三、常见的线程池
1、cachedThreadPool:无限大小的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
2、FixedThreadPool:固定大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
3、SingleThread:单线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
4、ScheduledThreadPool:能时间调度的的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
源码贴出来,很容易看出,致谢都是对ThreadPoolExecutor 构造方法的灵活应用,比对一下参数,这几种线程池的特性就很清楚了。
来来,你是不是可以自己组装自己特性的线程池了。