定义
线程池,就是一个线程的池子,里面有若干线程,它们的目的就是执行提交给线程池的任务,执行完一个任务后不会退出,而是继续等待或执行新任务执行完一个任务后不会退出,而是继续等待或执行新任务。
线程池主要是由两个概念组成:一个是任务队列;另一个是工作者线程。工作者线程主体就是一个循环,循环从队列中接受任务并执行,任务队列保存待执行的任务。
线程池的优点是显而易见的:
(1) 它可以重用线程,避免线程创建的开销。
(2)任务过多时,通过炮队避免创建过多的线程,减少系统资源消耗和竞争,确保任务有序完成。
理解线程池的一些参数
主要的构造方法
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,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, 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,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数corePoolSize、maximumPoolSize、keepAliveTime、unit用于控制线程池中线程的个数,workQueue 表示任务队列,threadFactor 用于对创建的线程进行一些配置,handler 表示任务拒绝策略。
线程池大小
maximumPoolSize 表示线程池的最多线程数,线程的个数会动态变化,但这是最大值,不管有多少任务,都不会创建比这个值最大的线程个数。
corePoolSize 表示线程池中的核心线程个数,不过,并不是一开始就创建这么多线程,刚创建一个线程池后,实际上并不会创建任何线程。
一般情况下,有新任务到来的时候,如果当前线程个数小于corePoolSize,就会创建一个新线程来执行该任务,需要说明的是,即使其他线程现在也是空闲的,也会创建新的线程。不过,如果线程个数大于等于corePoolsize,那就不会立即创建新线程了,它会先尝试排队,需要强调的是,它是“尝试排队”,而不是“阻塞等待” 入队,如果队列满了或其他原因不能立即入队,它就不会排队,而是检查线程的个数是否达到了maximumPoolSize ,如果没有,就会继续创建线程,直到线程数达到maximumPoolSize.
keepAliveTime 的目的是为了释放多余的线程资源,它表示,当线程池中的线程个数大于corePoolSize时,额外空闲线程的存活时间。也就是说,一个非核心线程,在空闲等待新任务时,会有一个最长等待时间,即keepAliveTime,如果到了时间还是没有新任务,就会被终止。如果该值为0,则表示所有线程都不会超时终止。
队列
ThreadPoolExecutor 要求的队列类型是阻塞队列BlockingQueue。
比如:
LinkedBlockingQueue:基于链表的阻塞队列,可以指定最大长度,但默认是无界的。
ArrayBlockingQueue:基于数组的有界阻塞队列。
PriorityBlockingQueue:基于堆的无界阻塞优先级队列。
SynchronousQueue:没有实际存储空间的同步阻塞队列。
如果用的是无界队列,需要强调的是,线程个数最多只能达到corePoolSize,到达corePoolSize后,新的任务总会排队,参数maximumPoolSize 也就没有意义了。
对于SynchronousQueue,我们知道,它没有实际存储元素的空间,当尝试排队时,只有正好有空闲线程在等待接受任务时,才会入队成功,否则,总是会创建新线程,直到达到maximumPoolSize。
任务拒绝策略
如果队列有界,且maximumPoolSize 有限,则当队列排满,线程个数也达到了了maximumPoolSize,这时,新任务来了,如何处理呢?此时,会触发线程池的任务拒绝策略。
提交任务的方法(execute/submit/invokeAll)会抛出异常,类型为RejectedException。
ThreadPoolExecutor实现了四种处理方式:
(1)ThreadPoolExecutor.AbortPolicy:这就是默认的方式没跑出异常。
(2)ThreadPoolExecutor.DiscardPolicy:静默处理,忽略新任务,不抛出异常,也不执行。
(3)ThreadPoolExecutor.DiscardOldestPolicy: 将等待时间最长的任务扔掉,然后自己排队。
(4)ThreadPoolExecutor.CallerRunsPolicy:在任务提交者线程中执行任务,而不是交给线程池中线程执行。
它们都是ThreadPoolExecutor 的public静态内部类,都实现了RejectedExecutionHandler接口,这个接口的定义为:
public interface RejectedExecutionHandler {
/**
* Method that may be invoked by a {@link ThreadPoolExecutor} when
* {@link ThreadPoolExecutor#execute execute} cannot accept a
* task. This may occur when no more threads or queue slots are
* available because their bounds would be exceeded, or upon
* shutdown of the Executor.
*
* <p>In the absence of other alternatives, the method may throw
* an unchecked {@link RejectedExecutionException}, which will be
* propagated to the caller of {@code execute}.
*
* @param r the runnable task requested to be executed
* @param executor the executor attempting to execute this task
* @throws RejectedExecutionException if there is no remedy
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
默认的RejectedExecutionHandler是一个AbortPolicy 实例。如下:
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
/**
* A handler for rejected tasks that throws a
* {@code RejectedExecutionException}.
*/
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
拒绝策略只有在队列有界,且maximumPoolSize 有限的情况下才会触发。如果队列无界,服务不了的任务总会排队,但这不一定是期望的结果,因为请求处理队列可能会消耗非常大的内存,甚至引发内存不够的异常,如果队列有界,但maximumPoolSize 无限,可能会创建过多的线程,占满CPU和内存,使得任何任务都难以完成。所以,在任务量非常大的场景中,让拒绝策略有机会执行是保证系统稳定运行很重要的方面。
线程工厂
线程池还可以接受一个参数,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);
}
根据Runnable 创建一个Thread。 ThreadPoolExecutor的默认实现是Executors类中的静态内部类DefaultThreadFactory,主要就是创建一个线程,给线程设置一个名称,设置daemon属性为false,设置线程优先级为标准默认优先级,线程名称的格式为:pool-<线程池编号>-thread-<线程编号>。如果需要自定义一些线程的属性,比如名称,可以实现自定义的ThreadFactory。
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
核心线程的特殊配置:
线程个数小于等于corePoolSize时,我们称这些线程为核心线程,默认情况下:
(1) 核心线程不会预先创建,只有当有任务时,才会创建。
(2)核心线程不会因为空闲而被终止,keepAliveTime 参数不适用于它。
//预先创建所有线程
public int prestartAllCoreThreads()
// 创建一个核心线程,如果所有核心线程都已创建,则返回false
public boolean prestartCoreThread()
// 如果参数为true,则keepAliveTime 参数也适用于核心线程
public void allowCoreThreadTimeOut(boolean value)
工厂类Executors
提供了一些静态的工厂方法,可以方便地预创建一些线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
只使用一个线程,使用无界队列LinkedBlockingQueue,线程创建后不会超时终止,该线程顺序执行所有任务。该线程池适用于需要确保所有任务被顺序执行的场合。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
使用固定数目的n个线程,使用无界队列LinkedBlockingQueue,线程创建后,不会超时终止。和newSingleThreadExecutor一样,由于是无界队列,如果排队任务过多,可能会消耗过多的内存。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
它的corePoolSize为 0 ,maximumPoolSize 为Integer.Max_Value, keepAliveTime 是60秒,队列为SynchronousQueue。它的含义是:当新任务到来时,如果正好有空闲线程在等待任务,则其中一个空闲线程接受该任务,否则就总是创建一个新线程,创建的总线程个数不受限制,对任一空闲线程,如果60秒内没有新任务,就终止。
实际中,应该使用newFixedThreadPool 还是newCachedThreadPool呢?
在系统负载很高的情况下,newFixedThreadPool 可以通过队列对新任务排队,保证有足够的资源处理实际的任务,而newCachedThreadPool 会为每个任务创建一个线程,导致创建过多的线程竞争CPU和内存资源,使得任何实际任务都难以完成,这时,newFixedThreadPool 更为适用。
不过,如果系统负载不是太高,单个任务的执行时间也比较短,newCachedThreadPool 的效率可能更高,因为任务可以不经排队,直接交给某一个空线程。
在系统负载可能极高的情况下,两者都不是好的选择,newFixedThreadPool的问题是队列过长,而newCachedThreadPool的问题是线程过多,这时,应根据具体情况自定义ThreadPoolExecutor,传递合适的参数。
线程池的死锁
任务之间有依赖,这种情况可能会出现死锁。比如任务A,在它的执行过程中,它给同样的任务执行服务提交了一个任务B,但需要等待任务B结束。
如果任务A是提交了一个单线程池,一定会出现死锁,A在等待B的结果,而B在队列中等待调度。如果是提交给了一个限定线程个数的线程池,也能因线程数限制出现死锁。
怎么解决这种问题呢?可以使用newCachedThreadPool创建线程池,让线程数不受限制。另一个解决方法是使用SynchronousQueue,它可以避免死锁,怎么做到的呢?对于普通队列,入队只是把任务放到了队列中,而对于SynchronousQueue来说,入队成功就意味着已有线程接受处理,如果入队失败,可以创建更多线程直到maximumPoolSize,如果达到了maximumPoolSize,会触发拒绝机制,不管怎么样,都不会死锁。