一、基础概念
- 不加控制和管理线程,对性能反尔会产生不利影响
- 可能产生创建和销毁线程所占用的时间大于该线程真实工作所消耗的时间
避免系统频繁创建和销毁线程,我们可以让创建的线程复用。在线程池中,总有那么几个活跃的线程。当你需要使用线程时,可以从池子里随便拿一个空闲线程,当完成工作时,并不着急关闭线程,而是将这个线程退回线程池,方便其他人使用。
二、JDK对线程池的支持
ThreadPoolExecutor表示一个线程池。Executors类则扮演线程池工厂的角色,通过Executors可以取得一个特定功能的线程池。ThreadPoolExecutor类实现了Executor接口,通过这个接口,任何Runnable的对象都可以被ThreadPoolExecutor线程池调度。
//该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务被暂存在一个任务队列中,待有线程空闲时,便处理任务队列中的任务。
public static ExecutorService newFixedThreadPool(int nThreads)
//该方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列任务
public static ExecutorService newSingleThreadExecutor()
//该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用
public static ExecutorService newCacheThreadPool()
//该方法返回一个ScheduledExecutorService对象,线程池大小是1.ScheduledExcutorsService接口在ExecutorService接口之上扩展了在给定时间执行某任务的功能,如在某个固定的延时之后执行,或者周期性执行某个任务
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
//该方法也返回一个ScheduledExecutorsService对象但可以指定线程池数量。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
三、刨根究底:核心线程池的内部实现
对于几个核心线程池的内部实现,均实现了ThreadPoolExecutor类,
//ThreadPoolExecutor构造函数
public ThreadPoolExecutor(
int corePoolSize, //指定了线程池中的线程数量
int maximumPoolSize, //指定了线程池中的最大线程数量
long keepAliveTime,//当线程池线程数量超过corePoolSize时,多余的空闲线程的存活时间,即超过corePoolSize的空闲线程,在多长时间内会被销毁
TimeUnit unit,//keepAliveTime的单位
BlockingQueue<Runnable> workQuque,//任务队列,被提交但尚未被执行的任务
ThreadFactory threadFactory,//线程工厂,用于创建线程,一般用默认的即可
RejectedExecutionHandler handler)//拒绝策略。当任务太多来不及处理时,如何拒绝任务。
workQueue
指被提交但未执行的任务队列,它是一个BlockingQueue接口的对象,仅用于存放Runnable对象。根据队列功能分类,在ThreadPoolExecutor类的构造函数中可使用以下几种BlockingQueue接口。
- 直接提交的队列:该功能由SynchronousQuene对象提供。SynchronousQuene是一个特殊的BlockingQuene。SynchronousQuene没有容量,每一个插入操作都要等待一个相应的删除操作,反之,每一个删除操作都要等待对应的插入操作。如果使用SynchronousQueue,则提交的任务不会被真实地保存,而总是将新任务提交给线程执行,如果没有空闲进程,则尝试创建新进程,如果进程数量已经达到最大值,则执行拒绝策略。因此,使用SynchronousQueue队列,通常要设置很大的maximumPoolSize值,否则很容易执行拒绝策略。
- 有界的任务队列:有界的任务队列可以使用ArrayBlockingQuque类实现。ArrayBlockingQueue类的构造函数必须带一个容量参数,表示该队列的最大容量:
public ArrayBlockingQueue(int capacity)
当使用有界任务队列时,若有新的任务需要执行,如果线程池的实际线程数小于corePoolSize,则会优先创建新的线程,若大于corePoolSize,则会将新任务加入等待队列。若等待队列已满,无法加入,则在总线程数不大于maximumPoolSize的前提下,创建新的进程执行任务。若大于maximumPoolSize,则执行拒绝策略。可见,有界队列仅当在任务队列装满时,才可能将线程数提升到corePoolSize以上,换言之,除非系统非常繁忙,否则要确保核心线程数维持在corePoolSize
- 无界任务队列:无界任务队列可以通过LinkedBlockingQueue类实现。与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况,当新的任务到来,系统的线程数小于corePoolSzie时,线程池会产生新的线程执行任务,但当系统的线程数达到corePoolSize后,就不会继续增加了。若后续仍有新的任务加入,而又没有空闲的线程资源,则任务直接进入队列等待。若任务创建和处理的速度差异很大,无界队列会保持快速增涨,直到耗尽系统资源
- 优先任务队列:优先任务队列是带有执行优先级的队列。它通过PriorityBlockingQueue类实现,可以控制任务的执行先后顺序。它是一个特殊的无界队列。无论是有界队列ArrayBlockingQueue类,还是未指定大小的无界队列LinkedBlockingQueue类都是按照先进先出算法处理任务的。而PriorityBlockingQueue类则可以根据任务自身的优先级顺序先后执行,在确保系统性能的同时,也能又很好的质量保证(总是确保高优先级的任务先执行)
回顾newFixedThreadPool()方法的实现,它返回了一个corePoolSize和maximumPoolSize大小一样的,并且使用了LinkedBlockingQueue任务队列的线程池。因为对于固定大小的线程池而言,不存在线程数量的动态变化,因此corePoolSize和maximumPoolSize可以相等。同时,它使用无界队列存放无法立即执行的任务,当任务提交非常频繁的时候,该队列可能迅速膨胀,从而耗尽资源。
newSingleThreadExecutor()方法返回的单线程线程池,是newFixedThreadPool()方法的一种退化,只是简单地将线程池线程数量设置为1。
newCacheThreadPool()方法返回corePoolSize为0,maximumPoolSize无穷大地线程池,这意味着在没有任务时,该线程池内无线程,而当任务被提交时,该线程池会使用空闲地线程执行任务,若无空闲线程,则将任务加入SynchronousQueue的队列,而SynchronousQueue队列是一种直接提交的队列,它总会迫使线程池增加新的线程执行任务。当任务执行完毕,由于corePoolSize为0,因此空闲线程又会在指定时间内60s被回收。
四、ThreadPoolExecutor核心调度代码
public void execute(Runnable command){
if(command == null)
throw new NullPointerException();
int c = ctl.get();
//workerCountOf()用于统计当前工作线程数量
//当小于现行核心线程数,继续执行线程。
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);
}