目录
创建调度线程池ScheduledExecutorService
keepAliveTime(数据类型long)与unit(数据类型TimeUnit)
threadFactory(数据类型ThreadFactory )
handler(数据类型RejectedExecutionHandler)
谁又能生而知之呢?谁都是从错到对,从不会到会。
在并发工具包与线程连接池中介绍了如何创建线程池,也给出了阿里巴巴的创建线程池规范,让我们先回顾一下。
阿里巴巴推荐用以下方式来创建线程池:
提示:这里只给出阿里巴巴推荐的创建线程池的方式,至于其它创建线程池的方式可详见并发工具包与线程连接池。
创建调度线程池ScheduledExecutorService:
需要引入依赖:
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
提示:SpringCloud项目,zuul等模块儿本身就依赖有commons-lang3依赖。
注:如果不想引入commons-lang3依赖,那么可以使用默认的线程工厂或者其它的线程工厂实现。
创建:
// 需要引入commons-lang3依赖
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory
.Builder()
.namingPattern("example-schedule-pool-%d")
.daemon(true)
.build());
创建公共线程池ExecutorService:
需要引入依赖:
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.0.1-jre</version>
</dependency>
提示: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<Runnable>(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);
}
}
直接在spring配置文件中配置线程池:
<bean id="userThreadPool"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="queueCapacity" value="2000" />
<property name="threadFactory" value= threadFactory />
<property name="rejectedExecutionHandler">
<ref local="rejectedExecutionHandler" />
</property>
</bean>
线程池构造函数各参数说明:
在回顾完阿里巴巴推荐如何创建线程池后,让我们再来看看各参数说明:
corePoolSize(数据类型int):
线程池的核心线程数。
注:当程序通过ExecutorService.execute(Runnable)或ExecutorService.submit(Callable)向线程池提交了新
的线程任务请求时,若已创建的core线程数少于corePoolSize时,(即使存在空闲的core线程,也)会创
建一个新的core线程来处理该请求。
注:当线程任务请求少于核心线程数时,(除非设置了allowCoreThreadTimeOut,否者)多出来的核心线程数
也不会被回收。
追注:若设置了allowCoreThreadTimeOut为true;那么当core线程空闲时间超过keepAliveTime时间后,
该core线程会被回收。
maximumPoolSize(数据类型int):
最大线程数。当core线程被线程任务占满时,若此时有新的线程任务进来,那么新的线程任务会被排进BlockingQueue<Runnable>队列中,若core线程被线程任务占满并且BlockingQueue<Runnable>队列也被线程排队排满了时。会判断maximumPoolSize值是否大于corePoolSize,如果maximumPoolSize不大于corePoolSize,那么线程池会按(RejectedExecutionHandler)策略拒绝执行该线程任务;如果maximumPoolSize大于corePoolSize,那么会创建新的线程。当线程任务少于core线程时,非core线程会被回收。
注:使用时maximumPoolSize应不小于corePoolSize(虽然理论上可以小于)。
注:创建的总线程数不能多于maximumPoolSize,若有多于maximumPoolSize的线程任务的话,那么会
按(RejectedExecutionHandler)策略拒绝执行那些多出来的线程任务。
keepAliveTime(数据类型long)与unit(数据类型TimeUnit):
设置线程超时时间。若某个非core线程的空闲时间超过了keepAliveTime的话,那么该线程会被回收;若某个core线程的空闲时间超过了keepAliveTime并且设置了allowCoreThreadTimeOut=true,那么该core线程也会被回收。
workQueue(数据类型BlockingQueue<Runnable>):
等待队列。当core线程满了时,如有新的线程任务进来,那么会优先考虑处放入此等待队列中;若此等待队列也满了,那么才会通过判断maximumPoolSize是否比corePoolSize大,来决定是否创建新的线程还是拒绝该线程任务了。
注:BlockingQueue<Runnable>的默认实现有以下几种:
-
SynchronousQueue
-
ArrayBlockingQueue
-
DelayQueue
-
LinkedBlockingDeque
-
LinkedBlockingQueue
-
LinkedTransferQueue
-
PriorityBlockingQueue
提示:关于这几种队列的详细介绍以及测试用例,会在下一篇博客中给出。
threadFactory(数据类型ThreadFactory ):
线程工厂。线程池利用该工厂创建线程。
注:java.util.concurrent包下的Executors类中提供有一个ThreadFactory 接口的默认实现内部类DefaultThreadFactory。
注:很多jar包也提供有自己对ThreadFactory 接口的实现。
handler(数据类型RejectedExecutionHandler):
线程任务拒绝策略。
注:RejectedExecutionHandler 的默认实现有以下四种:
-
ThreadPoolExecutor.AbortPolicy:抛出RejectedExecutionException异常。
-
ThreadPoolExecutor.CallerRunsPolicy:直接在原(调用ExecutorService.execute(Runnable)方法
原调用ExecutorService.submit(Callable)方法的)线程中去
运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。 -
ThreadPoolExecutor.DiscardOldestPolicy:它将放弃最旧的那一个线程任务请求,然后重试execute;
如果执行程序已关闭,则会丢弃该任务。 -
ThreadPoolExecutor.DiscardPolicy:默认情况下它将丢弃被拒绝的任务。
线程池各参数关系流程图:
声明:下述流程图整理自ThreadPoolExecutor.execute方法、ThreadPoolExecutor.addWorker方法、
ArrayBlockingQueue<E>.offer方法。
提示:不同的队列对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<E>.offer方法:
/**
* 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();
}
}