引入
并发编程作为提高性能十分重要的手段,是每一种程序语言的核心,因此java语言的并发编程在某种程度上体现了java语言的特性,jdk中java.util.concurrent工具包是java 并发编程中十分重要的部分
讲解
concurrent包基本结构分析
通过分析concurrent包的UML模型,如下一些类与其他类的关系比较密切,可以说这些类是concurrent包中的核心类,包括:
- interface Executor
- class CopyOnWriteArrayList implements List, RandomAccess, Cloneable, java.io.Serializable
- interface ExecutorService extends Executor
- class ThreadPoolExecutor extends AbstractExecutorService
- CompletionService
- abstract class ForkJoinTask implements Future, Serializable
- CompletableFuture implements Future, CompletionStage
- class ForkJoinPool extends AbstractExecutorService
- interface ConcurrentNavigableMap
Executor
ExecutorService
在Executor的基础上定义了线程池的接口,主要包括停止、关闭等:
void shutdown():关闭线程池,但是已经提交和正在运行的线程会继续运行完毕之后结束
List shutdownNow:关闭线程池,立即停止正在执行的任务,并且跳过全部已提交还未开始运行的任务,同时将这些任务返回。该方法不能够保障正在执行的的任务是完成执行还是停止。
- boolean isShutdown():判断线程池是否停止
- boolean isTerminated():在线程池shutdown之后,如果所有任务完成则返回true,在线程池第一次调用shutdown之前该方法均不会返回true
- boolean awaitTermination():阻塞至在shutdown请求之后全部任务执行完成,或者达到超时时间
- Future submit(Callable task)、 Future submit(Runnable task, T result)、Future
ThreadPoolExecutor
线程池对外的基础实现,从具体使用并可用的角度对ExecutorService进行实现,提供了一个最为基本的线程池实现,包括了线程池核心线程数量,最大支持数量等。
提供多个构造方法进行线程池的创建,基本构造方法如下。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- corePoolSize 核心线程池大小
- maximumPoolSize 最大线程池大小
- keepAliveTime 线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间
- TimeUnit keepAliveTime时间单位
- workQueue 阻塞任务队列
- threadFactory 新建线程工厂
- RejectedExecutionHandler 当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler来处理
重点讲解
1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliv
ForkJoinTask和ForkJoinPool
ForkJoinTask是从jdk7开始提供的Fork/Join框架中用于在ForkJoinPool线程池中执行的任务抽象类,有两个常有子类
- RecursiveTask :具有返回值
- RecursiveAction:不具有返回值
Fork/Join框架的核心思想是把一个大的任务拆分成多个小任务进行并行的处理,然后将小任务的计算结果进行合并,有点类似于map/reduce框架的思想。此外,Fork/Join框架在具体实现上采用了工作窃取算法。
工作窃取算法核心思想就是在对任务进行Fork时将大任务拆分相互之间无依赖关系的小任务,并将这些小任务划分到不同的工作队列中,不同的工作线程从对应的工作队列中获取小任务进行处理,如果有的工作队列中的任务已经执行完毕,则可以从其他工作队列中获取小任务进行执行,充分发挥工作线程的效率。
ForkJoinTask重点方法
- ForkJoinTask fork():fork就是不断分支的过程,在当前任务的基础上长出n多个子任务,ForkJoinTask将拆解出来的子任务放入工作队列中
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
- V join():
private int doJoin() {
Thread t; ForkJoinWorkerThread w; int s; boolean completed;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) {
if ((s = status) < 0)
return s;
if ((w = (ForkJoinWorkerThread)t).unpushTask(this)) {
try {
completed = exec();
} catch (Throwable rex) {
return setExceptionalCompletion(rex);
}
if (completed)
return setCompletion(NORMAL);
}
return w.joinTask(this);
}
else
return externalAwaitDone();
}
ForkJoinPool重点变量和方法
应用
扩展
发散
ThradPoolExecutor与银行营业大厅
对于ThradPoolExecutor的设计比较类似于银行办理业务的设计,首先可以对关键对象进行类似的对比
corePoolSize<----->正常对外开发的窗口数量
maximumPoolSize<----->包括备份在内的全部窗口数量
workqueue<----->营业大厅
task<----->业务办理
submmit方法<----->大堂经理处理
keepAliveTime<----->备份窗口保持时间
银行正常情况下会提供一定数量大小的窗口(corePoolSize)进行业务办理,当新进来客户发现现在全部窗口都已经有其他人正在办理业务时,则会在营业大厅(workqueue)进行等待,根据叫号的顺序依次进行业务的办理;如果营业大厅可容纳的等待人数满了,并且仍然有人需要办理业务,这个时候银行会把备份的窗口开启用来提供业务办理服务;如果可用的备份窗口(maximumPoolSize)均已使用完成,则大堂经理会拒绝客户进行进入营业大厅。
如果某个备份的窗口在一段时间内(keepAliveTime)没有任何客户进行业务的办理,银行则会关闭该备份窗口。
这两者有部分的具体业务情况也存在部分差异:
银行营业大厅在每天开业时将窗口全部打开才允许客户进来办理业务,而对于ThradPoolExecutor则是在有一个task进来才创建一个thread进行处理
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
ForkJoinTask与点钞任务
ForkJoinTask的设计思想类似于银行的点钞任务
ForkJoinPool<----->点钞室
ForkJoinTask<----->点钞任务
WorkQueue<----->钞票盒
银行点钞室再进行点钞任务时将大量的钞票快速拆分成扎之后分发到不同的钞票盒中,与钞票盒对应的点钞机首先清点该钞票盒中的成扎的钞票,如果某一个钞票盒中钞票扎已经清点完毕,对应的工作人员会从其他钞票盒中将未清点的钞票获取过来进行清点,直至全部钞票盒中的钞票扎均清点完毕