池化资源使得资源可以重复利用,提高响应速度,例如常见的数据库连接池。
我们不用手动创建和销毁线程,池会帮我们管理,另外还可以对线程监控。
理解线程的时候,要把线程Thread当作一个载体,而Runnable是一个任务,任务完成后,线程还可以执行其他任务
线程类相关的UML类图
最重要的是中间那个ThreadPoolExecutor,简单说下ScheduledThreadPoolExecutor和ForkJoinPool
ScheduledThreadPoolExecutor是一个可调度的线程池,可以设置多久之后线程开始运行,以及运行的频率
ForkJoinPool是一个支持并行化的线程池,接受ForkJoinTask,将一个任务分拆成多个子任务,每个任务分配到不同的cpu上执行,计算结果再通过join去合并
最下面的Executors是一个工具类,用来生成不同类型的线程池,最常用的就是以下三种:
- newFixedThreadPool:固定大小
- newCachedThreadPool:无界线程池,可以进行自动线程回收
- newSingleThreadExecutor:单个后台线程池
这几种类型的线程池底层都是ThreadPoolExecutor,这个类有以下属性:
- int corePoolSize :线程池基本大小
- int maximumPoolSize :线程池最大大小
- long keepAliveTime :单个线程保持活动时间
- TimeUnit unit :保持活动时间单位
- BlockingQueue workQueue :任务等待队列
- ThreadFactory threadFactory :线程工厂
- RejectedExecutionHandler handler :驳回回调
这个类里有最重要的一个方法void execute(Runnable),先理解了这个方法,对理解上面三种类型的线程池大有益处
execute()方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
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);
}
注释写的很清楚- step1:如果当前池中已经存在的线程比corePoolSize数量少,那么直接用addWorker这个方法new一个新线程,并将当前任务交给这个新线程。addWorker方法会检查线程池的运行状态和当前存活的线程数量,如果不应该新增线程则返回false,进入step2
- step2:如果能将任务加到Queue中,仍需要做第二次检测,以防止在step1中检查的时候刚好有一个存活的线程在进入step2中销毁掉了,这种情况就要新new一个线程;还需要判断线程池是否在running,如果stop掉了,则将刚才入队的任务从队列中删除,因为线程池如果被中断,后面加进来的任务一律不运行,否则进入step3
- step3:如果我们不能将任务加到Queue中,就试着new一个新线程,如果失败了,那就说明Queue饱和了,或者线程池shut down
如果execute失败,则要运行reject方法,根据不同的RejectedExecutionHandler 执行不同的操作,ThreadPoolExecutor默认的RejectedExecutionHandler 是AbortPolicy,即抛出异常,还有其余三种实现有兴趣的童鞋可以去看看
了解execute是如何工作的,再来看那三种线程池
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看到newFixedThreadPool的corePoolSize和maximumPoolSize的值都是nThreads;keepAliveTime设置为0,因为当newFixedThreadPool实例化时,线程池中的线程数量就已经固定,不存在线程回收的问题;workQueue 选择了LinkedBlockingQueue,是一个无界的队列,在execute时,可以无限向Queue中添加任务,这有可能会消耗掉所有内存。也就是说,任务数如果大于corePoolSize,则多出的任务就得在Queue中排队等待,直到有线程执行完,才去Queue中取任务执行
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
无界线程当然大小不能为固定值了,要设置为0,maximumPoolSize要设置成最大值MAX_VALUE,workQueue 选择了SynchronousQueue,这个Queue有个特点,在添加的同时必须有其他线程取走才能继续添加,因此在execute中向这个Queue执行offer操作永远是失败的,也就是说,newCachedThreadPool会一直走到step3才能创建新线程,但是无止尽的创建线程肯定是对资源的浪费,因此keepAliveTime设置成了60秒,即空闲线程超过60秒的自动销毁
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
其实就是newFixedThreadPool的特殊版,corePoolSize和maximumPoolSize的值都设为1,但这里有个有意思的地方,ThreadPoolExecutor被FinalizableDelegatedExecutorService修饰了,因为ThreadPoolExecutor有些方法例如setCorePoolSize、setMaximumPoolSize,而newSingleThreadExecutor不希望上层应用修改这些值,因此做了一层包装,掩盖了这些方法
以上