ThreadPoolExecutor参数解析
之前学习线程池,发现线程池大致有四种创建方法:
- newFixedThreadPool 创建一个指定大小的线程池
- newCachedThreadPool 创建一个可缓冲的线程池
- newSingleThreadExecutor 创建一个仅有一个线程的线程池。
- newScheduledThreadPool 创建一个可周期性调度任务的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
观察他们的源码可以发现底层都是调用的 ThreadPoolExecutor。
阿里巴巴的 java 开发规范不建议使用这四种线程池创建方式,因为第二个参数最大线程数为 Integer.MAX_VALUE,或者任务队列无上界,造成大量的任务或大量的线程,从而造成 OOM。
比如 newFixedThreadPool 与 newSingleThreadExecutor 的阻塞队列容量都是 Integer.MAX_VALUE。而 newCachedThreadPool 与 newScheduledThreadPool 的最大线程数量都是 Integer.MAX_VALUE , 这些都会造成巨大的问题。
强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式。
下面主要解析ThreadPoolExecutor的创建方式
参数介绍:
-
corePoolSize 核心线程数:
核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。
核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。 -
maximumPoolSize 最大线程数
当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会启用拒绝策略。
-
keepAliveTime 空闲线程的存活时间
当线程数量大于corePoolSize核心线程数,并有线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。
-
unit
keepAliveTime的时间单位
-
workQueue
任务队列,当没有空闲线程池的时候,将任务添加到任务队列中,等待线程空闲再去执行。
-
threadFactory
线程工厂,用于配置创建线程方式。如更改线程池中创建的线程名字等。
-
handler
线程池对拒绝任务的处理策略,当线程数量达到最大线程数量并且任务队列已满,就会执行拒绝策略。
注意:
线程池有一个属性private volatile boolean allowCoreThreadTimeOut;
用于控制核心线程空闲时间会否会超时。
可用这个方法在线程池创建之后,使用之前来设置。默认为false.
pool.allowCoreThreadTimeOut(boolean value);
设置控制核心线程是否会超时和终止的策略,如果在keep-alive时间内没有任务到达,则在新任务到达时替换。当为false时,核心线程永远不会终止,因为缺少传入的任务。当为true时,同样适用于非核心线程的keep-alive策略也适用于核心线程。为了避免持续的线程替换,当设置为true时,keep-alive时间必须大于零。通常应该在池被积极使用之前调用此方法。
测试实例
通过具体的例子,来分析一下这些参数,以及查看一下线程池中任务提交流程。
查看线程池线程创建,任务队列,拒绝策略
import jdk.nashorn.internal.runtime.regexp.JoniRegExp;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* @author xiaogang.zhang
*/
public class ThreadPoolExecutorTest {
public static class RunTask implements Runnable {
private String taskName;
public RunTask(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " [" + this.taskName + "] " + " running!");
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " [" + this.taskName + "] " + " is over!");
}
}
public static void main(String[] args) {
//创建线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(4, 6, 20,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), new ThreadFactory() {
final private AtomicInteger atomicInteger = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
//以原子方式将当前值加 1。
int index = atomicInteger.incrementAndGet();
System.out.println("create Thread: " + "Thread-" + index);
return new Thread(r, "Thread-" + index);
}
}, new ThreadPoolExecutor.AbortPolicy());
//设置核心线程永不过时
pool.allowCoreThreadTimeOut(false);
for (int i = 1; i <= 20; i++) {
pool.submit(new RunTask("task-" + i));
}
//关闭线程池
pool.shutdown();
}
}
上面的代码设定了线程池的参数:
核心线程数=4,
最大线程数=6,
空闲线程存活时间为20s,
任务队列为ArrayBlockingQueue容量为2,有界的阻塞数组
使用线程工厂来观测线程创建时机以及指定线程的名称。
拒绝策略为ThreadPoolExecutor.AbortPolicy(),处理程序会抛出一个RejectedExecutionException异常。
提价了20个任务
查看运行结果:
从运行结果中可以看出:
- 刚开始提交任务的时候,线程池中还没有线程,线程池开始创建线程,直到线程数量够用或者达到核心线程数量。然后核心线程开始执行提交的任务(task-1、task-2、task-3、task-4)。
- 核心线程刚开始创建了四个,对应执行提交的任务(task-1、task-2、task-3、task-4),后面提交的任务就开始加到任务队列中。可以看到编号5的位置开始执行任务(task-5、task-6),这两个任务就是加入到任务队列中的,直到有空闲线程才开始执行。
- 当线程数量达到核心线程数,并且核心线程都没有空闲,任务队列也已经满了的时候,就开始创建新的线程来执行提交的任务。线程数量增加到了最大线程数量。
- 当新的任务提交时,此时线程数量达到了最大线程数量,并且没有线程空闲,任务队列也已经满了,就会启用拒绝策略,代码中的为ThreadPoolExecutor.AbortPolicy(),就会直接抛出RejectedExecutionException异常。
查看空闲线程的自动销毁
更改一下代码,让任务数为8,并且主线程睡眠30s,因为之前设定的空闲线程存活时间为20s。30s后查看线程池的线程数量。
for (int i = 1; i <= 8; i++) {
pool.submit(new RunTask("task-" + i));
}
TimeUnit.SECONDS.sleep(30);
System.out.println(pool.getPoolSize());
查看运行结果:
发现线程数量又回到了核心线程数量。程序运行期间,由于任务队列已满,又新建了线程来处理任务,线程数量=最大线程数量。
当线程数量>核心线程数量的时候,如果线程处于空闲状态的时间超过keepAliveTime,时间单位为unit,该线程就会自动退出销毁。所以最后线程的数量就会收缩到核心线程数量。
线程池任务提交原理总结
- 当线程数小于核心线程数时,会创建线程来处理任务。
- 当线程数大于等于核心线程数时,如果任务队列没有满,将任务加进任务队列等待线程空闲时调用。
- 当线程数大于等于核心线程数,并且任务队列已满时,创建新的线程来执行任务(须保证线程数不大于最大线程数)。
- 当有线程调用结束,处于空闲状态,如果任务队列中有任务,则执行任务队列中的任务。
- 如果线程数大于核心线程数,并且线程空闲时间超过keepAliveTime,则该线程退出销毁。以保证线程数量收缩为核心线程数。
阻塞队列
-
ArrayBlockingQueue
由数组组成的有界阻塞队列。
容量一旦创建,后续无法修改。
元素是有顺序的,按照先入先出进行排序,从队尾插入数据数据,从队头拿数据;
队列满时,往队列中 put 数据会被阻塞,队列空时,往队列中拿数据也会被阻塞。public ArrayBlockingQueue(int capacity) { this(capacity, false); } /** * Creates an {@code ArrayBlockingQueue} with the given (fixed) * capacity and the specified access policy. * * @param capacity the capacity of this queue * @param fair if {@code true} then queue accesses for threads blocked * on insertion or removal, are processed in FIFO order; * if {@code false} the access order is unspecified. * @throws IllegalArgumentException if {@code capacity < 1} */ public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); }
第二个方法有两个参数,第一个参数为阻塞队列容量,第二个参数是否公平主要是读写锁是否公平。如果是公平锁,那么在锁竞争时,就会按照先来先到的顺序,如果是非公平锁,锁竞争时随机的。
初始容量<=0会抛出IllegalArgumentException异常。
-
LinkedBlockingQueue
有界链表阻塞队列。底层数据结构就是一个链表,链表保存了头节点和尾节点。
public LinkedBlockingQueue() { this(Integer.MAX_VALUE); } /** * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity. * * @param capacity the capacity of this queue * @throws IllegalArgumentException if {@code capacity} is not greater * than zero */ public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node<E>(null); } /** * Creates a {@code LinkedBlockingQueue} with a capacity of * {@link Integer#MAX_VALUE}, initially containing the elements of the * given collection, * added in traversal order of the collection's iterator. * * @param c the collection of elements to initially contain * @throws NullPointerException if the specified collection or any * of its elements are null */ public LinkedBlockingQueue(Collection<? extends E> c) { this(Integer.MAX_VALUE); final ReentrantLock putLock = this.putLock; putLock.lock(); // Never contended, but necessary for visibility try { int n = 0; for (E e : c) { if (e == null) throw new NullPointerException(); if (n == capacity) throw new IllegalStateException("Queue full"); enqueue(new Node<E>(e)); ++n; } count.set(n); } finally { putLock.unlock(); } }
默认不传参时队列容量时 Integer.MAX_VALUE。
链表维护先入先出队列,新元素被放在队尾,获取元素从队头部拿;public void put(E e) throws InterruptedException { if (e == null) throw new NullPointerException(); // Note: convention in all put/take/etc is to preset local var // holding count negative to indicate failure unless set. int c = -1; Node<E> node = new Node<E>(e); final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; putLock.lockInterruptibly(); try { /* * Note that count is used in wait guard even though it is * not protected by lock. This works because count can * only decrease at this point (all other puts are shut * out by lock), and we (or some other waiting put) are * signalled if it ever changes from capacity. Similarly * for all other uses of count in other wait guards. */ while (count.get() == capacity) { notFull.await(); } enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); } private void enqueue(Node<E> node) { // assert putLock.isHeldByCurrentThread(); // assert last.next == null; last = last.next = node; }
-
DelayQueue
延迟队列。本质上是个优先级队列,按照剩余时间的多少排序的队列。 -
SynchronousQueue
队列不存储数据,所以没有大小,也无法迭代;
插入操作的返回必须等待另一个线程完成对应数据的删除操作,反之亦然;
队列由两种数据结构组成,分别是后入先出的堆栈和先入先出的队列,堆栈是非公平的,队列是公平的。
拒绝策略
- AbortPolicy
中止策略
该策略是默认饱和策略。
抛出一个RejectedExecutionException异常。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
- DiscardPolicy
抛弃策略,直接丢弃不做任何操作。
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
修改测试用例代码
ThreadPoolExecutor pool = new ThreadPoolExecutor(4, 6, 20,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), new ThreadFactory() {
final private AtomicInteger atomicInteger = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
//以原子方式将当前值加 1。
int index = atomicInteger.incrementAndGet();
System.out.println("create Thread: " + "Thread-" + index);
return new Thread(r, "Thread-" + index);
}
},
// new ThreadPoolExecutor.AbortPolicy()
new ThreadPoolExecutor.DiscardPolicy()
);
for (int i = 1; i <= 10; i++) {
pool.submit(new RunTask("task-" + i));
}
运行发现后面提交的任务都被抛弃了。task-9、task-10并没有
- DiscardOldestPolicy
抛弃旧任务策略,
将头任务出队。
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
修改测试用例
//创建线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(4, 6, 20,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), new ThreadFactory() {
final private AtomicInteger atomicInteger = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
//以原子方式将当前值加 1。
int index = atomicInteger.incrementAndGet();
System.out.println("create Thread: " + "Thread-" + index);
return new Thread(r, "Thread-" + index);
}
},
// new ThreadPoolExecutor.AbortPolicy()
// new ThreadPoolExecutor.DiscardPolicy()
new ThreadPoolExecutor.DiscardOldestPolicy()
);
运行发现task-9和task-10都运行了,但是最早入队的task-5,task-6被抛弃了。
- CallerRunsPolicy
调用者运行。
直接调用Runnable的run方法运行。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
修改测试用例
//创建线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(4, 6, 20,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), new ThreadFactory() {
final private AtomicInteger atomicInteger = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
//以原子方式将当前值加 1。
int index = atomicInteger.incrementAndGet();
System.out.println("create Thread: " + "Thread-" + index);
return new Thread(r, "Thread-" + index);
}
},
// new ThreadPoolExecutor.AbortPolicy()
// new ThreadPoolExecutor.DiscardPolicy()
// new ThreadPoolExecutor.DiscardOldestPolicy()
new ThreadPoolExecutor.CallerRunsPolicy()
);
运行发现task-9,task-10是main线程执行的。