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
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个任务
查看空闲线程的自动销毁
更改一下代码,让任务数为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,该线程就会自动退出销毁。所以最后线程的数量就会收缩到核心线程数量。
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
队列不存储数据,所以没有大小,也无法迭代;
插入操作的返回必须等待另一个线程完成对应数据的删除操作,反之亦然;
队列由两种数据结构组成,分别是后入先出的堆栈和先入先出的队列,堆栈是非公平的,队列是公平的。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
/**
* 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并没有
/**
* 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被抛弃了。
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线程执行的。