一、 Executor接口
public interface Executor {
void execute(Runnable command);
}
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable>shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T>submit(Callable<T> task);
<T> Future<T>submit(Runnable task, T result);
Future<?>submit(Runnable task);
<T> List<Future<T>>invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
<T> List<Future<T>>invokeAll(Collection<? extends Callable<T>> tasks,long timeout,
TimeUnit unit)throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
public abstract class AbstractExecutorService implements ExecutorService {
....
}
二、 ThreadPoolExecutor
public class ThreadPoolExecutor extends AbstractExecutorService {
...
}
构造方法:
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
....
}
corePoolSize -- 线程池维护的核心线程数,即使它们处于空闲状态;线程可以预先创建或延迟创建,如果是延迟创建,则当有任务来临时才会创建;
maximumPoolSize -- 线程池中允许存在的最大线程数;
keepAliveTime -- 超额线程的空闲存活时间;默认情况下,当线程池中的线程个数超出了corePoolSize,那么空闲的线程一旦超出额定的存活时间就会被终止,以节省系统资源;
unit -- 参数keepAliveTime的单位;
workQueue -- 通过execute()提交的任务(Runnable)在被执行前,保存在workQueue中;
threadFactory -- 当需要创建一个新的线程时,使用这个参数来创建;
handler -- 当线程阻塞或线程数量达到workQueue的上限时,使用这个handler来处理。
1. 线程创建和任务提交的条件和步骤当一个新任务被提交给ThreadPoolExecutor的时候,处理流程大概是这样的:
(1)如果当前线程池中运行的线程数目低于corePoolSize,则创建新线程执行提交的任务,而无需检查当前是否有空闲线程;
(2) 如果提交任务时线程池中线程数已大于等于corePoolSize,则将提交的任务加入等待执行队列;
(3) 如果提交任务时等待执行的任务队列是有限队列,而且已满,或者无法将任务添加到队列中(SynchronousQueue),则在线程池中开辟新线程执行此任务;
(4) 如果线程池中线程数目已达到maximumPoolSize,则提交的任务交由RejectedExecutionHandler处理。
默认情况下,ThreadPoolExecutor的线程数是根据需求来延迟初始化的,即有新任务加进来的时候才会挨个创建线程。
除此之外,线程池执行器也提供了提前创建初始化线程的方法:
public boolean prestartCoreThread()
public int prestartAllCoreThreads()
分别是预先创建一个线程和预先创建线程直到线程数到达核心线程数corePoolSize。
2. 线程数目的维护
刚刚提到,ThreadPoolExecutor有corePoolSize和maximum两个变量来维护线程池中的线程个数,提交任务的时候会有线程数目的增长,那线程的个数又是怎么来维护的呢。构造方法里还有两个参数,分别是keepAlive和unit,这两个参数确定了一个时间间隔,也就是空闲线程存活的时间间隔。默认情况下,当线程池中的线程个数超出了corePoolSize,那么空闲的线程一旦超出额定的存活时间就会被终止,以节省系统资源。在JDK1.6之后,增加了allowsCoreThreadTimeOut这个boolean属性和读写属性的方法,用来标志核心线程空闲超时是否也可以终止掉。如果allowCoreThreadTimeout为true, 则核心线程如果空闲超时时也会被终止,直到线程数为0。
2. 线程入队列和任务丢弃原则简述
除了前面描述涉及到的四个属性和ThreadFactory之外,还有两个分别是workQueue和handler,分别是BlockingQueue和RejectedExecutionHandler类型。
BlockingQueue只是一个接口,它所表达的是当队列为空或者已满的时候,需要阻塞以等待生产者/消费者协同操作并唤醒线程。其有很多不同的具体实现类,各有特点。有的可以规定队列的长度,也有一些则是无界的。
排队策略:
2.1 SynchronousQueue(直接提交):CachedThreadPool使用的是这个BlockingQueue,它将任务直接提交给线程而不保持它们。如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。通常要求无界 maximumPoolSizes以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
适用场景:此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。
2.2 LinkedBlockingQueue(无界队列):FixedThreadPool和SingleThreadExecutor使用的是这个BlockingQueue,当所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过corePoolSize。因此,maximumPoolSize 的值也就无效了。
适用场景:当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列
2.3 ArrayBlockingQueue(有界队列):当使用有限的 maximumPoolSizes 时,有界队列有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
当然,开发者也可以定制ThreadPoolExecutor时使用ArrayBlockingQueue有界队列。
对于任务丢弃,ThreadPoolExecutor以内部类的形式实现了4个策略。分别是:
(1)CallerRunsPolicy:提交任务的线程自己负责执行这个任务。即不会创建新的线程,就在主线程中执行这个任务;
(2)AbortPolicy:使Executor抛出异常,通过异常做处理。
(3)DiscardPolicy:丢弃请求被拒绝的任务(discards therejected task)。
(4)DiscardOldestPolicy:丢弃掉队列中最早加入的任务,即后面的任务会挤走前面的任务。从而最终被执行的是末尾提交的任务。
在调用构造方法时,参数中未指定RejectedExecutionHandler情况下,默认采用AbortPolicy。
Executors类的几个静态方法:
/**
* 适用场景:保证先提交的任务先执行。
* 每次提交任务时,如果没有空闲的线程,才会创建1个新线程。如果有某一线程超过
* 60s处于空闲状态,则将它移除。这种线程池可能没有线程存在,因此能节省资源,提
* 高性能,适合生命周期短(short-lived)的异步任务。SynchronousQueue的意思是每个插入
*操作必须等待另一个线程的对应移除操作,就是缓冲区为1的生产者消费者模式。
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
/**
* 创建一个线程池,维持最多nThreads个线程,如果有多余的任务,则会在无界队列
* LinkedBlockingQueue中等待,直到有1个线程可用(available)。若某一线程在执行期间
* 由于某种原因而执行失败,先于shutdown就terminates,则1个新的线程被创建执行接
* 下来的工作。
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
/**
* 创建一个线程池,只有不多于1个线程处于active状态,剩下的任务在无界队列
* LinkedBlockingQueue排队。若该线程在执行期间由于某种原因而执行失败,
* 先于shutdown就terminates,则1个新的线程被创建来取代它。
* 该方法与newFixedThreadPool(1)不同,后者是可以重新配置更改的,而前者则不能。
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}