为什么要使用线程池
Thread是一个重量级的资源,创建、启动以及销毁都是比较耗费系统资源的,因此对线程的重复利用是一种非常好的程序设计。所以我们让任务在线程池中运行比为每一个任务分配一个线程更有优势。
线程池的原理
线程池,字面理解就是一个管理工作线程的资源池。线程池与工作队列是密切相关的。工作队列中缓存了要执行的任务。工作线程的任务很简单:从工作队列中取出任务,执行任务,返回线程池等待下一个任务。当有任务的时候,如果池中有线程空闲,会直接执行任务。否则将任务缓存到工作队列,等待池中线程空闲来执行。如果池中线程都在执行任务并且工作队列已满。则拒绝任务。
线程池的处理流程
结合execute方法的源码来看
public void execute(Runnable command){
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);
}
}
JDK提供的线程池
可以通过Executors中的静态工厂方法来创建线程池
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads){
/*创建一个固定程度的线程池,每提交一个任务创建一个线程
直到线程的最大数量。使用的是LinkedBlockingQueue无界队列
当线程池中的线程都在工作时,有任务提交时缓存在无界队列中
*/
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newCachedThreadPool
//创建一个可缓存的线程池
//当有任务来的时候,如果线程池中没有可用线程,就创建线程来执行任务
//当线程池比较空闲的时候,会移除超过60秒没有使用的线程
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newSingleThreadExecutor
//单线程的线程池类似于newFixedThreadPool(1)
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newScheduledThreadPool
//创建一个固定线程的线程池,而且以延迟或者定时的方式来执行任务
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
ThreadPoolExecutor
可以看到上面使用Executors创建线程池底层都还是使用ThreadPoolExecutor来创建线程池的,先看看ThreadPoolExecutor的一些参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory
RejectedExecutionHandler handler)
corePoolSize
线程池的核心线程数量,在没有任务执行时,线程池的大小。只有在工作队列满了
的时候才会创建超出这个数量的线程。但不出超过最大线程数。
maximumPoolSize:
线程池允许创建的最大线程数,即可同时活动线程数量的上下
long keepAliveTime,TimeUnit unit
线程活动的保持时间和线程活动的保持时间单位,这两个参数是配合起来使用的。
如果某个线程的空闲超过了存活时间
线程池的核心线程数、最大线程数和空闲时间共同负责线程创建和销毁。通过调节线程的核心线程数和存活时间,可以帮助线程池回收空闲线程占用的资源。
BlockingQueue:工作队列,缓存等待执行的任务
基本的任务队列有三种:有界队列、无界队列和同步移交。队列的选择和其它参数有关
newFixedThreadPool默认情况下使用无界的LinkedBlockingQueue。如果所有的工
作线程都处于忙碌状态,那么任务就将在任务队列中等待。如果有任务持续的到来,
那么任务会无限制的增加。
更稳妥的应该是使用有界队列。比如ArrayBlockingQueue。有界队列可以避免
资源耗尽,但是又会产生新的问题工作队列满了怎么办?可以使用饱和策略来解决。
在使用有界队列时,队列的大小应和线程池大小一起调节。如果线程池较小,而队列比
较大,可以减少内存使用量,降低CPU使用率,减少上下文切换。但是会降低系统
吞吐量。
对于非常大或者无界的线程池,可以使用SynchronousQueue来避免任务排队
newCachedThreadPool就使用的SynchronousQueue。
SynchronousQueue并不是真正意义上的队列,而是一种在线程之间进行移交的机制
要将任务放在SynchronousQueue中,必须有线程正在等待接收这个任务。
如果没有线程等待,线程核心数小于线程池可活动线程上限时,会创建一个线程。否则
根据饱和策略,任务会被拒绝。
RejectedExecutionHandler :饱和策略。当队列和线程都满了,那么必须采用一种策略处理提交的新任务。
当有界队列满了之后,饱和策略开始发挥作用。JDK提供了几种不同的
RejectedExecutionHandler实现。
AbortPolicy:默认饱和策略。该策略抛出未检查的RejectedExecutionException
调用者可以捕获这个异常,根据需求来编写处理代码
DiscardPolicy:会抛弃掉任务
DiscardOldestPolicy:抛弃下一个将被执行的任务,然后尝试提交新任务
CallerRunsPolicy:既不会抛弃任务也不会产生异常。而是将任务退回给调用者
ExecuteService的生命周期
ExecuteService的生命周期有三种状态:运行、关闭和终止
ExecuteService在初始创建时处于运行状态
shutdown方法将执行平缓的关闭:不再接收新的任务,同时等待已经提交的任务执行
完毕--包括那些还没开始的任务。
shutdownNow执行比较粗暴的关闭:尝试取消所有运行中的任务,并不再启动队
等所有任务执行完成后,ExecuteService将转为终止状态