Executor、Executors、ExecutorService是不是傻傻分不清?
常用构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
参数介绍:
三种类型的ThreadPoolExecutor:
下面我们分别看一下这三种ThreadPoolExecutor
源代码如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
其中corePool和maximumPoolSize 都被设置成指定的参数,而keepAliveTime设置为0L,意味着多余的空闲线程会被立即终止
因为是LinkedBlockingQueue无界队列(长度Integer.MAX_VALUE),会有如下影响:
特点: 使用单个线程的Executor,源代码如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
其中corePool和maximumPoolSize 都被设置成了1,其他参数与FixedThreadPool相同
示意图如下:
影响和运行方式都与FixedThreadPool相同,这里就不再赘述
源代码如下:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
可以看到核心线程数为0,而最大线程池容量却是MAX,这意味着没有空闲线程就会不断的创建线程去执行,极端情况会耗尽CPU和内存资源,相反由于60s后空闲线程会被终止,所以长时间内保持空闲的情况下不会占用任何资源
SynchronousQueue是一个没有容量的阻塞队列,每一个插入操作都会等待另一个线程对应的取出操作
示意图如下:
ThreadPoolExecutor是可扩展的,它提供了几个可以在子类中改写的方法:beforeExecute、afterExecute、terminated。在执行任务的线程中将调用这些方法,我们可以在这些方法内添加日志、计时、监视或统计信息收集的功能。如下:
public class MyThreadPoolExecutor extends ThreadPoolExecutor {
public MyThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public MyThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}
public MyThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
public MyThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
System.out.println("任务执行前");
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
System.out.println("任务执行后");
}
@Override
protected void terminated() {
System.out.println("线程池关闭的时候");
super.terminated();
}
}
ScheduledThreadPoolExecutor主要用来延迟之后执行任务或者定时执行任务,继承自ThreadPoolExecutor。功能与Timer类似,但ScheduledThreadPoolExecutor要更加强大、灵活。Timer对应的是单个线程,而ScheduledThreadPoolExecutor可以指定多个线程
源代码如下:
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
ScheduledThreadPoolExecutor为了实现周期性任务对ThreadPoolExecutor做了如下修改:
如上图所示,该线程池核心方法有三个,源代码如下:
/**
* 延迟执行一个异步任务
* command 异步任务
* delay 延迟时间
* unit 时间单位
*/
public ScheduledFuture> schedule(Runnable command, long delay, TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture> t = decorateTask(command,
new ScheduledFutureTask(command, null, triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
/**
* 延迟执行一个有返回值的异步任务
* callable 有返回值的异步任务
* delay 延迟时间
* unit 时间单位
*/
public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) {
if (callable == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture t = decorateTask(callable,
new ScheduledFutureTask(callable, triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
/**
* 延迟执行一个有周期性的异步任务
* command 异步任务
* initialDelay 延迟时间
* period 周期时间(每隔多长时间执行,不会考虑任务自身的运行时间)
* unit 时间单位
*/
public ScheduledFuture> scheduleAtFixedRate(Runnable command, long initialDelay,
long period,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (period <= 0)
throw new IllegalArgumentException();
ScheduledFutureTask sft =
new ScheduledFutureTask(command, null, triggerTime(initialDelay, unit),
unit.toNanos(period));
RunnableScheduledFuture t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
/**
* 延迟执行一个有周期性的异步任务
* command 异步任务
* initialDelay 延迟时间
* delay 周期时间(每隔多长时间执行,会在任务自身执行完后才开始计时)
* unit 时间单位
*/
public ScheduledFuture> scheduleWithFixedDelay(Runnable command, long initialDelay,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (delay <= 0)
throw new IllegalArgumentException();
ScheduledFutureTask sft =
new ScheduledFutureTask(command, null, triggerTime(initialDelay, unit),
unit.toNanos(-delay));
RunnableScheduledFuture t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
可以看到4个方法都创建了ScheduledFutureTask这个任务类,而且都执行了delayedExecute()方法:
private void delayedExecute(RunnableScheduledFuture> task) {
if (isShutdown())
reject(task);
else {
super.getQueue().add(task);
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
里面最关键的就是把ScheduledFutureTask任务添加到了任务队列中;
我们看看和正常的线程池相比,该线程池take()到底有什么不同DelayedWorkQueue.take():
除了从头部取任务以外,还增加了时间的判断
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
}
public RunnableScheduledFuture> take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture> first = queue[0];
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
// 任务的time-当前时间<=0 即代表到时间该执行了
if (delay <= 0)
// 所以将任务从队列中取出执行
return finishPoll(first);
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}
因为是周期性任务,所以在执行后还需要将任务重新设置好时间再放入队列,ScheduledFutureTask.run():
public void run() {
// 判断任务是否是周期性任务 0:否 !=0:是
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
ScheduledFutureTask.super.run();
else if (ScheduledFutureTask.super.runAndReset()) {
//设置下一次执行时间
setNextRunTime();
// 重新将任务放入队列
reExecutePeriodic(outerTask);
}
}
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p);
}
void reExecutePeriodic(RunnableScheduledFuture> task) {
if (canRunInCurrentRunState(true)) {
// 放入队列
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
在任务add()入队的时候,如果头部已经存在任务,则会执行sifUp()方法:
说白了就是再每次入队的时候都会进行一次任务的时间对比,将要入队的任务找到在队列中合适的位置再插入进去,也就是队列中的任务都是根据执行时间有着先后顺序的
private void siftUp(int k, RunnableScheduledFuture> key) {
while (k > 0) {
int parent = (k - 1) >>> 1;
RunnableScheduledFuture> e = queue[parent];
// 对比任务的time时间 将改任务插入到队列中合适的执行位置
if (key.compareTo(e) >= 0)
break;
queue[k] = e;
setIndex(e, k);
k = parent;
}
queue[k] = key;
setIndex(key, k);
}
public int compareTo(Delayed other) {
if (other == this) // compare zero if same object
return 0;
if (other instanceof ScheduledFutureTask) {
ScheduledFutureTask> x = (ScheduledFutureTask>)other;
long diff = time - x.time;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
else if (sequenceNumber < x.sequenceNumber)
return -1;
else
return 1;
}
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
这得从往线程池提交一个任务说起了,execute()方法如下:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 1.小于核心线程数
if (workerCountOf(c) < corePoolSize) {
// 则添加一个核心工作线程 worker ,true代表核心工作线程
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2.大于核心线程数则将任务放入阻塞队列中
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);
}
// 3.大于核心线程数且队列加入失败(队列满了),则添加一个非核心线程
else if (!addWorker(command, false))
// 非核心线程添加失败了(达到最大线程数),则执行拒绝策略
reject(command);
}
从上面可以看到提交一个任务的时候其实不是立马执行的,会有核心线程数、最大线程数、队列长度等判断,任务会被放入Worker工作线程中执行,线程池内部有一个Workers 的set集合用来管理所有的工作线程,所以说任务的最终执行时在Worker.run() 中的,复用的秘密也在这里面:
通过getTask()不断获取任务,然后执行,到了这你会发现核心线程和非核心线程好像一点区别都没有是吧?让我们看看getTask()方法:
private Runnable getTask() {
// 超时标记
boolean timedOut = false;
// 死循环
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// 判断是否超过核心线程数
// 超过了则会为true 下面将进行超时判断
// false则代表核心线程,下面将一直阻塞获取任务
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 通过timed来判断是阻塞获取 还是 一定时间内获取返回
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// 超时了,上面的判断将会跳出
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
该方法可谓是核心线程和非核心线程最大的区别了:是否超时退出?
核心线程数:将会调用workQueue.take()方法,如果队列没有任务将会一直阻塞在这里,有任务则会取出执行,这就是核心线程复用的原理
非核心线程数:将会调用workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)方法,在达到keepAliveTime时间后将会返回null,代表超时了,最外层run方法的while循环也将会跳出,所以非核心线程会被回收
这里也需要注意一个点,因为默认核心线程难被回收,所以在使用的时候需要注意核心线程内所持有的对象内存回收问题
不一定,在上述源代码中可以看到判断中有一个allowCoreThreadTimeOut 变量,该变量默认是false,但是我们可以设置成true,那么核心线程也可以在keepAliveTime时间到后退出;
还有就是在代码中可以看到worker工作线程是没有明确的标识核心线程和非核心线程的,所以说核心线程只是保留的工作线程数量,在这个数量下都是核心线程,看哪个线程先被判断而已:
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
线程池里面的线程退出后,做了些什么处理?
线程退出都会执行processWorkerExit()方法:
源代码如下:
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 如果是异常退出,需要把工作线程数量减一
// 正常退出的话,在getTask方法里面已经减一了
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 计算线程池已完成任务总数
// 已完成任务总数+当前退出线程所完成的任务数
completedTaskCount += w.completedTasks;
// 在工作线程集合中移除
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
// 下面判断就是为了维持线程池中最小工作线程数量
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
谨慎使用FixedThreadPool、SingleThreadExecutor、CachedThreadPool三种池子:
建议通过ThreadPoolExecutor指定参数创建
其实前面已经说了ThreadPoolExecutor的拓展功能,可以利用这个实现一些监控和告警规则,如: