[超级链接:Java并发学习系列-绪论]
本章主要对ForkJoin并发框架进行学习,主要内容分为三个部分:
ForkJoin并发框架:Fork=分解 + Join=合并
ForkJoin并发框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割(Fork)成若干个小任务,最终汇总(Join)每个小任务结果后得到大任务结果的框架。
例如:计算1+2+…1000000000,可以将其分割(Fork)为100000个小任务,每个任务计算10000个数据的相加,最终汇总(Join)这100000个小任务的计算结果进行合并,得到计算结果。
ForkJoin并发框架是采取工作窃取(Work-Stealing)算法实现的。
工作窃取算法:某个线程从其他线程的工作队列中窃取任务来执行。可以形象的用下面的图表示:
下面来详细说明工作窃取算法(模拟):
优缺点
ForkJoin并发框架应用了两种十分重要的编程思想:
ForkJoin并发框架的主要类包括:
根据ForkJoinTask的两种类型,可以将ForkJoin并发框架划分为两种用法:
场景说明:
重点分析:
代码:
/**
* ForkJoin框架实例1-RecursiveAction-无返回值-数据交换
* 数据交换:专网A内的数据库DB1上有100万数据,需要通过数据交换服务发送到专网B的数据库DB2上。
* 1.原来的做法:将这100万数据按照5000条一组进行分组,然后每组都通过一个线程进行发送。不知道什么原因,总之经常会出现重复发送的数据。
* 2.新的做法:根据ForkJoin框架编程思想,将这100万数据按照阈值THRESHOLD进行子任务划分,然后依次发送。
*
* @author hanchao 2018/4/15 19:26
**/
public class RecursiveActionDemo {
private static final Logger LOGGER = Logger.getLogger(RecursiveActionDemo.class);
//模拟数据库DB2
static ConcurrentLinkedQueue DB2 = new ConcurrentLinkedQueue();
/**
* 定义一个数据交换任务,继承自RecursiveAction,用于发送数据交换的JSON数据
*
* @author hanchao 2018/4/15 19:28
**/
static class DataExchangeTask extends RecursiveAction {
//阈值=5000
private static final int THRESHOLD = 5000;
//开始索引
private int start;
//结束索引
private int end;
//交换的数据
List list;
public DataExchangeTask(int start, int end, List list) {
this.start = start;
this.end = end;
this.list = list;
}
@Override
protected void compute() {
//如果当前任务数量在阈值范围内,则发送数据
if (end - start < THRESHOLD) {
//发送Json数据
try {
sendJsonDate(this.list);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//如果当前任务数量超出阈值,则进行任务拆分
int middle = (start + end) / 2;
//左边的子任务
DataExchangeTask left = new DataExchangeTask(start, middle, list);
//右边的子任务
DataExchangeTask right = new DataExchangeTask(middle, end, list);
//并行执行两个“小任务”
left.fork();
right.fork();
}
}
/**
* 发送数据
*
* @author hanchao 2018/4/15 20:04
**/
private void sendJsonDate(List list) throws InterruptedException {
//遍历
for (int i = start; i < end; i++) {
//每个元素都插入到DB2中 ==> 模拟数据发送到DB2
DB2.add(list.get(i));
}
//假定每次发送耗时1ms
Thread.sleep(1);
}
}
/**
* 模拟从数据库中查询数据并形成JSON个是的数据
*
* @author hanchao 2018/4/15 20:21
**/
static void queryDataToJson(List list) {
//随机获取100万~110万个数据
int count = RandomUtils.nextInt(1000000, 1100000);
for (int i = 0; i < count; i++) {
list.add("{\"id\":\"" + UUID.randomUUID() + "\"}");
}
}
/**
* RecursiveAction-无返回值:可以看成只有fork没有join
*
* @author hanchao 2018/4/15 19:26
**/
public static void main(String[] args) throws InterruptedException {
//从数据库中获取所有需要交换的数据
List dataList = new ArrayList();
queryDataToJson(dataList);
int count = dataList.size();
LOGGER.info("1.从DB1中读取数据并存放到List中,共计读取了" + count + "条数据.");
//DB2的数据量
LOGGER.info("2.开始时,DB2中的数据量:" + DB2.size());
LOGGER.info("3.通过ForkJoin框架进行子任务划分,并发送数据");
//定义一个ForkJoin线程池
ForkJoinPool forkJoinPool = new ForkJoinPool();
//定义一个可分解的任务
DataExchangeTask dataExchangeTask = new DataExchangeTask(0, count, dataList);
//向ForkJoin线程池提交任务
forkJoinPool.submit(dataExchangeTask);
//线程阻塞,等待所有任务完成
forkJoinPool.awaitTermination(5, TimeUnit.SECONDS);
//任务完成之后关闭线程池
forkJoinPool.shutdown();
//查询最终传输的数据量
LOGGER.info("4.结束时,DB2中的数据量:" + DB2.size());
//查询其中一条数据
LOGGER.info("5.查询其中一条数据:" + DB2.peek());
}
}
运行结果:
2018-04-17 23:38:05 INFO - 1.从DB1中读取数据并存放到List中,共计读取了1037606条数据.
2018-04-17 23:38:05 INFO - 2.开始时,DB2中的数据量:0
2018-04-17 23:38:05 INFO - 3.通过ForkJoin框架进行子任务划分,并发送数据
2018-04-17 23:38:10 INFO - 4.结束时,DB2中的数据量:1037606
2018-04-17 23:38:10 INFO - 5.查询其中一条数据:{"id":"85bc8085-4836-41e3-b7f4-d80f38d8f0fe"}
运行结果,表明发送数据成功。
场景说明:
重点分析:
代码:
/**
* ForkJoin框架-RecursiveTask-有返回值-超大集合分割计算
* 计算N,N+1,N+2....N+Max的和
* 第一种方式:单线程计算
* 第二种方式:ForkJoin并发计算
* @author hanchao 2018/4/15 21:31
**/
public class RecursiveTaskDemo {
private static final Logger LOGGER = Logger.getLogger(RecursiveTaskDemo.class);
/**
* 超大集合计算任务-泛型类
*
* @author hanchao 2018/4/15 21:34
**/
static class LargeSetComputeTask extends RecursiveTask {
//阈值
private static final int THRESHOLD = 100000;
private int start;//开始下标
private int end;//结束下标
public LargeSetComputeTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
//如果当前任务的计算量在阈值范围内,则直接进行计算
if (end - start < THRESHOLD) {
return computeByUnit();
} else {//如果当前任务的计算量超出阈值范围,则进行计算任务拆分
//计算中间索引
int middle = (start + end) / 2;
//定义子任务-迭代思想
LargeSetComputeTask left = new LargeSetComputeTask(start, middle);
LargeSetComputeTask right = new LargeSetComputeTask(middle, end);
//划分子任务-fork
left.fork();
right.fork();
//合并计算结果-join
return left.join() + right.join();
}
}
/**
* 最小计算单元进行计算
*
* @author hanchao 2018/4/15 21:39
**/
private long computeByUnit() {
long sum = 0L;
for (int i = start; i < end; i++) {
sum += i;
}
return sum;
}
}
/**
* ForkJoin框架-RecursiveTask
* 1.有返回值:可用Future接口进行结果获取
* 2.RecursiveTask需要fork和join并用
*
* @author hanchao 2018/4/15 21:44
**/
public static void main(String[] args) throws ExecutionException, InterruptedException {
//计算(0+1+2+3+1000000000)*2的结果
int count = 1000000001;
//第一种方式:单线程计算
long start1 = System.currentTimeMillis();
LOGGER.info("1.第一种计算方式--单线程计算");
long result = 0L;
for (long i = 0; i < count; i++) {
result += i;
}
LOGGER.info("1.计算结果:" + result + ",用时:" + (System.currentTimeMillis() - start1) + "ms.\n");
//通过ForkJoin框架进行子任务计算
long start2 = System.currentTimeMillis();
LOGGER.info("2.第二种计算方式--ForkJoin框架计算");
//定义ForkJoinPool线程池
ForkJoinPool forkJoinPool = new ForkJoinPool();
//定义计算任务
LargeSetComputeTask computeTask = new LargeSetComputeTask(0, count);
//提交计算任务
Future future = forkJoinPool.submit(computeTask);
//执行完任务关闭线程池
forkJoinPool.shutdown();
//输出计算结果:
LOGGER.info("2.计算结果:" + future.get() + ",用时:" + (System.currentTimeMillis() - start2) + "ms.");
}
}
运行结果:
2018-04-17 23:52:45 INFO - 1.第一种计算方式--单线程计算
2018-04-17 23:52:45 INFO - 1.计算结果:500000000500000000,用时:338ms.
2018-04-17 23:52:45 INFO - 2.第二种计算方式--ForkJoin框架计算
2018-04-17 23:52:45 INFO - 2.计算结果:500000000500000000,用时:213ms.
运行结果说明两种方式计算结果都正确,fork+join效率高。
上面的两个实例对ForkJoin并发框架的编程方式进行了入门介绍。
为了更加全面的了解ForkJoin并发框架,下面对ForkJoinPool的常用方法进行简单的罗列:
/**
* ForkJoin-ForkJoinPool的方法学习
*
* @author hanchao 2018/4/15 22:12
**/
public class ForkJoinPoolBasicDemo {
/**
* ForkJoin-ForkJoinPool的方法学习
*
* @author hanchao 2018/4/15 22:14
**/
public static void main(String[] args) {
//构造函数
//无参:并行级别=Runtime.getRuntime.availableProcessors();
ForkJoinPool forkJoinPool = new ForkJoinPool();
//指定并行级别
ForkJoinPool forkJoinPool1 = new ForkJoinPool(4);
//提交任务(返回计算情况)
//ForkJoinTask implements Future, Serializable
//提交Runnable任务
Runnable runnable = null;
forkJoinPool.submit(runnable);
//提交Runnable + result任务
Integer result = null;
Future future2 = forkJoinPool.submit(runnable, result);
//提交Callable任务
Callable callable = null;
Future future3 = forkJoinPool.submit(callable);
//提交ForkJoinTask任务
ForkJoinTask forkJoinTask = null;
Future future4 = forkJoinPool.submit(forkJoinTask);
//提交RecursiveAction任务(RecursiveAction extends ForkJoinTask)
RecursiveAction recursiveAction = null;
forkJoinPool.submit(recursiveAction);
//提交RecursiveTask任务(RecursiveTask extends ForkJoinTask)
RecursiveTask recursiveTask = null;
Future future6 = forkJoinPool.submit(recursiveTask);
//提交任务(不返回计算情况)
//提交Runnable任务
Runnable runnable1 = null;
forkJoinPool.execute(runnable1);
//提交ForkJoinTask任务
ForkJoinTask forkJoinTask1 = null;
forkJoinPool.execute(forkJoinTask);
//提交RecursiveAction任务(RecursiveAction extends ForkJoinTask)
RecursiveAction recursiveAction1 = null;
forkJoinPool.execute(recursiveAction);
//提交RecursiveTask任务(RecursiveTask extends ForkJoinTask)
RecursiveTask recursiveTask1 = null;
forkJoinPool.execute(recursiveTask);
//提交任务(返回计算结果)
//提交ForkJoinTask任务
ForkJoinTask forkJoinTask2 = null;
Integer result1 = forkJoinPool.invoke(forkJoinTask);
//提交RecursiveAction任务(RecursiveAction extends ForkJoinTask)
RecursiveAction recursiveAction2 = null;
forkJoinPool.invoke(recursiveAction);
//提交RecursiveTask任务(RecursiveTask extends ForkJoinTask)
RecursiveTask recursiveTask2 = null;
Integer result3 = forkJoinPool.invoke(recursiveTask);
//提交任务集
//获取最先计算完成的-阻塞
List> callableList = new ArrayList>();
try {
Integer result4 = forkJoinPool.invokeAny(callableList);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//获取最先计算完成的-阻塞-可超时
try {
Integer result5 = forkJoinPool.invokeAny(callableList, 1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
//所有任务计算完成之后,返回结果-阻塞
List> futureList = forkJoinPool.invokeAll(callableList);
//所有任务计算完成之后,返回结果-阻塞-可超时
try {
List> futureList1 = forkJoinPool.invokeAll(callableList, 1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
//是否正在终止
forkJoinPool.isTerminating();
//是否终止
forkJoinPool.isTerminated();
try {
//等待终止
forkJoinPool.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
//是否休眠
forkJoinPool.isQuiescent();
//等待休眠
forkJoinPool.awaitQuiescence(1, TimeUnit.SECONDS);
//存在等待执行的子任务
forkJoinPool.hasQueuedSubmissions();
//是否是FIFO模式
boolean asyncMode = forkJoinPool.getAsyncMode();
//获取当前活跃线程数
int activeThreadCount = forkJoinPool.getActiveThreadCount();
//获取线程池并行级别
int parallelism = forkJoinPool.getParallelism();
//获取工作线程数量
int poolSize = forkJoinPool.getPoolSize();
//获取等待执行的子任务数量
int queuedSubmissionCount = forkJoinPool.getQueuedSubmissionCount();
//获取等待执行的任务数量
long queuedTaskCount = forkJoinPool.getQueuedTaskCount();
//获取非阻塞的活动线程数量
int runningThreadCount = forkJoinPool.getRunningThreadCount();
//获取窃取线程数量
long stealCount = forkJoinPool.getStealCount();
//获取工作线程工厂
ForkJoinPool.ForkJoinWorkerThreadFactory threadFactory = forkJoinPool.getFactory();
//获取未捕获异常处理器
Thread.UncaughtExceptionHandler handler = forkJoinPool.getUncaughtExceptionHandler();
//关闭线程池
forkJoinPool.isShutdown();
forkJoinPool.shutdown();
forkJoinPool.shutdownNow();
}
}