目录
阿里巴巴推荐用以下方式来创建线程池
创建调度线程池ScheduledExecutorService
创建公共线程池ExecutorService
直接在spring配置文件中配置线程池
线程池构造函数各参数说明
corePoolSize(数据类型int)
maximumPoolSize(数据类型int)
keepAliveTime(数据类型long)与unit(数据类型TimeUnit)
workQueue(数据类型BlockingQueue)
threadFactory(数据类型ThreadFactory )
handler(数据类型RejectedExecutionHandler)
线程池各参数关系流程图
谁又能生而知之呢?谁都是从错到对,从不会到会。
在并发工具包与线程连接池中介绍了如何创建线程池,也给出了阿里巴巴的创建线程池规范,让我们先回顾一下。
提示:这里只给出阿里巴巴推荐的创建线程池的方式,至于其它创建线程池的方式可详见并发工具包与线程连接池。
需要引入依赖:
org.apache.commons
commons-lang3
3.8.1
提示:SpringCloud项目,zuul等模块儿本身就依赖有commons-lang3依赖。
注:如果不想引入commons-lang3依赖,那么可以使用默认的线程工厂或者其它的线程工厂实现。
创建:
// 需要引入commons-lang3依赖
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory
.Builder()
.namingPattern("example-schedule-pool-%d")
.daemon(true)
.build());
需要引入依赖:
com.google.guava
guava
27.0.1-jre
提示:SpringCloud项目,eureka、zuul等模块儿本身就依赖有guava依赖。
注:如果不想引入guava依赖,那么可以使用默认的线程工厂或者其它的线程工厂实现。
创建:
// ThreadFactoryBuilder需要引入guava依赖
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
// 创建Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 50, 3000L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(20),
namedThreadFactory,
new ThreadPoolExecutor.AbortPolicy());
// 执行任务
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown
使用示例:
/**
* 虽然我们可以通过Excutors创建线程池,但是推荐:我们自己手动创建线程池
*
* @author JustryDeng
* @date 2018/12/29 13:58
*/
public class CreateThreadPool {
private static final Object OBJ = new Object();
private static Integer count = 10000;
public static void main(String[] args) {
try {
createThreadPoolTest();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void createThreadPoolTest() throws InterruptedException {
int length = 10000;
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("justry-deng-pool-%d").build();
ExecutorService executorService = new ThreadPoolExecutor(5, 50,
2000L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10),
namedThreadFactory,
new ThreadPoolExecutor.AbortPolicy());
for (int i = 1; i <= length; i++) {
// 使用lambel表达式简单实现Runnable接口的run方法
executorService.execute(() -> {
// 只有获得了object的锁的线程,才能操作
synchronized (OBJ) {
System.out.println(Thread.currentThread().getName());
count--;
}
});
}
// 当线程池中所有线程都运行完毕后,关闭线程池
executorService.shutdown();
// 主线程阻塞2秒再输出count的值,为了避免输出打印count的值时,其余线程还没计算完;导致输出的不是count的最终值
Thread.sleep(2000);
System.out.println(count);
}
}
在回顾完阿里巴巴推荐如何创建线程池后,让我们再来看看各参数说明:
线程池的核心线程数。
注:当程序通过ExecutorService.execute(Runnable)或ExecutorService.submit(Callable)向线程池提交了新
的线程任务请求时,若已创建的core线程数少于corePoolSize时,(即使存在空闲的core线程,也)会创
建一个新的core线程来处理该请求。
注:当线程任务请求少于核心线程数时,(除非设置了allowCoreThreadTimeOut,否者)多出来的核心线程数
也不会被回收。
追注:若设置了allowCoreThreadTimeOut为true;那么当core线程空闲时间超过keepAliveTime时间后,
该core线程会被回收。
最大线程数。当core线程被线程任务占满时,若此时有新的线程任务进来,那么新的线程任务会被排进BlockingQueue
注:使用时maximumPoolSize应不小于corePoolSize(虽然理论上可以小于)。
注:创建的总线程数不能多于maximumPoolSize,若有多于maximumPoolSize的线程任务的话,那么会
按(RejectedExecutionHandler)策略拒绝执行那些多出来的线程任务。
设置线程超时时间。若某个非core线程的空闲时间超过了keepAliveTime的话,那么该线程会被回收;若某个core线程的空闲时间超过了keepAliveTime并且设置了allowCoreThreadTimeOut=true,那么该core线程也会被回收。
阻塞队列。当core线程满了时,如有新的线程任务进来,那么会优先考虑处放入此队列中进行等待;若此队列也满了,那么才会通过判断maximumPoolSize是否比corePoolSize大,来决定是否创建新的线程还是拒绝该线程任务了。
注:阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:
1、在队列为空时,获取元素的线程会等待队列变为非空。
2、当队列满时,存储元素的线程会等待队列可用。
注:BlockingQueue
ArrayBlockingQueue:一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。
默认情况下不保证问者公平的访问队列。
注:所谓公平访问队列是指阻塞的所有生产者线程或消费者线程在队列可用时,可以按照阻塞的先后顺序访问队
列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素。
通常情况下为了保证公平性会降低吞吐量。
LinkedBlockingQueue:是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为 Integer.MAX_VALUE。
此队列按照先进先出的原则对元素进行排序。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列,所谓双向队列指的你可以从队列的两端插入和移
出元素。
SynchronousQueue:是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加
元素。SynchronousQueue 可以看成是一个传球手,负责把生产者线程处理的数据直接传
递给消费者线程。队列本身并不存储任何元素,非常适合于传递性场景, 比如在一个线程中
使用的数据,传递给另外一个线程使用,SynchronousQueue 的吞吐量高于
LinkedBlockingQueue 和 ArrayBlockingQueue。
DelayQueue:是一个支持延时获取元素的无界阻塞队列。队列使用 PriorityQueue 来实现。队列中的元素必须实
现 Delayed 接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能
从队列中提取元素。我们可以将 DelayQueue 运用在以下应用场景,如:
A、缓存系统的设计:可以用 DelayQueue 保存缓存元素的有效 期,使
用一个线程循环查询 DelayQueue,一旦能从 DelayQueue 中获取
元素时,表示缓存有效期到了。
B、定时任务调度。使用 DelayQueue 保存当天将会执行的任务和执行时
间,一旦从 DelayQueue 中获取到任务就开始执行,从比如
TimerQueue 就是使用 DelayQueue 实现的。
注:队列中的Delayed必须实现compareTo来指定元素的顺序。比如让延时时间最长的放在队列的末尾。
LinkedTransferQueue:一个由链表结构组成的无界阻塞 TransferQueue 队列。相对于其他阻塞队列
LinkedTransferQueue 多了 tryTransfer 和 transfer 方法。
PriorityBlockingQueue:是一个支持优先级的无界队列。默认情况下元素采取自然顺序排列,也可以通
过比较器 comparator 来指定元素的排序规则。
特别声明:上述阻塞队列的知识摘录自方腾飞(花名清英)的文章,更多信息可详
见https://www.infoq.cn/article/java-blocking-queue,强烈建议阅读。
线程工厂。线程池利用该工厂创建线程。
注:java.util.concurrent包下的Executors类中提供有一个ThreadFactory 接口的默认实现内部类DefaultThreadFactory。
注:很多jar包也提供有自己对ThreadFactory 接口的实现。
线程任务拒绝策略。
注:RejectedExecutionHandler 的默认实现有以下四种:
ThreadPoolExecutor.AbortPolicy:抛出RejectedExecutionException异常。
ThreadPoolExecutor.CallerRunsPolicy:直接在原(调用ExecutorService.execute(Runnable)方法
原调用ExecutorService.submit(Callable)方法的)线程中去
运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。
ThreadPoolExecutor.DiscardOldestPolicy:它将放弃最旧的那一个线程任务请求,然后重试execute;
如果执行程序已关闭,则会丢弃该任务。
ThreadPoolExecutor.DiscardPolicy:默认情况下它将丢弃被拒绝的任务。
声明:下述流程图整理自ThreadPoolExecutor.execute方法、ThreadPoolExecutor.addWorker方法、
ArrayBlockingQueue
提示:不同的队列对offer方法的实现各有不同,但核心仍然是:将线程任务放入队列。
给出上述流程图相关核心方法:
提示:以下源码来自jdk1.8。
ThreadPoolExecutor.execute方法:
/**
* Executes the given task sometime in the future. The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.
*
* @param command the task to execute
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
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);
}
ThreadPoolExecutor.addWorker方法:
/**
* Checks if a new worker can be added with respect to current
* pool state and the given bound (either core or maximum). If so,
* the worker count is adjusted accordingly, and, if possible, a
* new worker is created and started, running firstTask as its
* first task. This method returns false if the pool is stopped or
* eligible to shut down. It also returns false if the thread
* factory fails to create a thread when asked. If the thread
* creation fails, either due to the thread factory returning
* null, or due to an exception (typically OutOfMemoryError in
* Thread.start()), we roll back cleanly.
*
* @param firstTask the task the new thread should run first (or
* null if none). Workers are created with an initial first task
* (in method execute()) to bypass queuing when there are fewer
* than corePoolSize threads (in which case we always start one),
* or when the queue is full (in which case we must bypass queue).
* Initially idle threads are usually created via
* prestartCoreThread or to replace other dying workers.
*
* @param core if true use corePoolSize as bound, else
* maximumPoolSize. (A boolean indicator is used here rather than a
* value to ensure reads of fresh values after checking other pool
* state).
* @return true if successful
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
ArrayBlockingQueue
/**
* Inserts the specified element at the tail of this queue if it is
* possible to do so immediately without exceeding the queue's capacity,
* returning {@code true} upon success and {@code false} if this queue
* is full. This method is generally preferable to method {@link #add},
* which can fail to insert an element only by throwing an exception.
*
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}