在JDKl.7 版本中提供了 Fork/Join 并行执行任务框架,它的主要作用是把大任务分割成若干个小任务,再对每个小任务得到的结果进行汇总, 此种开发方法也叫分治编程,分治编程可以极大地利用 CPU 资源,提高任务执行的效率,也是目前与多线程有关的前沿技术。
Fork-Join 分治编程与类结构
JDK 中并行执行框架 Fork-Join 使用了“工作窃取(work-stealing)”算法,它是指某个线程从其他队列里窃取任务来执行,那这样做有什么优势或者目的是什么呢?比如要完成一个比较大的任务,完全可以把这个大的任务分割为若干互不依赖的子任务/小任务,为了更加方便地管理这些任务,于是把这些子任务分别放到不同的队列里,这时就会出现有的线程会先把自己队列里的任务快速执行完毕,而其他线程对应的队列里还有任务等待处理,完成任务的线程与其等着,不如去帮助其他线程分担要执行的任务,于是它就去其他线程的队列里窃取一个任务来执行,这就是所谓的“工作窃取(work-stealing )”算法。JDKl.7 中实现分治编程需要使用 ForkJoinPool 类,此类的主要作用是创建一个任务池。
ForkJoinPool 继承关系
class ForkJoinPool extends AbstractExecutorService
abstract classAbstractExecutorService implements ExecutorService
interface ExecutorService extends Executor
1.类ForkJoinPool提供的功能是一个任务池,而执行具体任务却不是 ForkJoinPool,而是ForkJoinTask 类
2.ForkJoinTask是抽象类,不能实例化,所以需要该类的3个子类 CountedCompleter、RecursiveAction、RecursiveTask来实现具体的功能。
3.RecursiveAction:用于没有返回结果的任务;RecursiveTask:用于有返回结果的任务
ForkJoinPool 实现求和实验
使用RecursiveTask,子任务返回求和,父任务join获取,最终最顶层任务返回总和。
1-9求和示例
public class ForkJoinDemo {
public static class MyRecursiveTask extends RecursiveTask<Integer> {
//拆分任务的阀值
private int threshold = 1;
//开始位置
private int startIndex;
//结束位置
private int endIndex;
public MyRecursiveTask(int startIndex, int endIndex) {
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
protected Integer compute(){
if (endIndex - startIndex > threshold) {
//拆分任务
int midIndex = (endIndex + startIndex)/2;
//拆分为2个子任务
MyRecursiveTask left = new MyRecursiveTask(startIndex,midIndex);
MyRecursiveTask right = new MyRecursiveTask(midIndex+1,endIndex);
//执行两个子任务
this.invokeAll(left,right);
Integer leftValue = 0;
Integer rightValue = 0;
//获取子任务所得的和,join方法堵塞,可将异常抛出。get()方法,异常必须处理
leftValue = left.join();
rightValue = right.join();
//返回拆分后两个子任务求和之和
return leftValue + rightValue;
} else {
//不需要拆分直接返回计算和
if (endIndex == startIndex) {
return endIndex;
} else {
return endIndex + startIndex;
}
}
}
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = null;
try {
forkJoinPool = new ForkJoinPool();
//提交任务
ForkJoinTask task = forkJoinPool.submit(new MyRecursiveTask(1,9));
//获取最终的和,输出
System.out.println(task.join());
} catch (Exception e) {
System.out.println("main线程捕获了异常!");
e.printStackTrace();
}
}
}
45
ForkJoinPool类execute()方法
1.void execute(ForkJoinTask<?> task)
2.void execute(Runnable task)
3.execute方法参数支持ForkJoinTask和Runnable ,但是无返回值。如果获取任务的结果,ForkJoinTask可以通过自身join()或get()获取,Runnable 不行。
示例
public class ForkJoinDemo {
public static class MyRecursiveTask extends RecursiveTask<Integer> {
//拆分任务的阀值
private int threshold = 1;
//开始位置
private int startIndex;
//结束位置
private int endIndex;
public MyRecursiveTask(int startIndex, int endIndex) {
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
protected Integer compute(){
if (endIndex - startIndex > threshold) {
//拆分任务
int midIndex = (endIndex + startIndex)/2;
//拆分为2个子任务
MyRecursiveTask left = new MyRecursiveTask(startIndex,midIndex);
MyRecursiveTask right = new MyRecursiveTask(midIndex+1,endIndex);
//执行两个子任务
this.invokeAll(left,right);
Integer leftValue = 0;
Integer rightValue = 0;
//获取子任务所得的和,join方法堵塞,可将异常抛出。get()方法,异常必须处理
leftValue = left.join();
rightValue = right.join();
//返回拆分后两个子任务求和之和
return leftValue + rightValue;
} else {
//不需要拆分直接返回计算和
if (endIndex == startIndex) {
return endIndex;
} else {
return endIndex + startIndex;
}
}
}
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = null;
try {
forkJoinPool = new ForkJoinPool();
MyRecursiveTask myRecursiveTask = new MyRecursiveTask(1,9);
/**************************execute提交任务*************************************/
forkJoinPool.execute(myRecursiveTask);
//通过myRecursiveTask.join()获取最终的和,输出
System.out.println(myRecursiveTask.join());
//通过myRecursiveTask.get()获取最终的和,输出
System.out.println(myRecursiveTask.get());
} catch (Exception e) {
System.out.println("main线程捕获了异常!");
e.printStackTrace();
}
}
}
45
45
ForkJoinPool类submit()方法
1.ForkJoinTask submit(ForkJoinTask task),有返回值,get(),join()方法获取返回值,会堵塞
2.ForkJoinTask submit(Callable task),有返回值,get(),join()方法获取返回值,会堵塞
3.ForkJoinTask submit(Runnable task, T result), Runnable 参数不会返回值,但是可以将result作为参数传进去,利用Runnable 对参数result进行修改,这样主方法获取到的 result就是最新的,使用这种方式产生返回值。
4.ForkJoinTask<?> submit(Runnable task) ,传人 Runnable 接口没有返回值,但如果调用 get()方法依然呈阻塞状态,等待 Runnable 结束再返回null。
方法 public List<Future<*T>>invokeAll(Collection<? extends Callable<*T>> tasks )的使用
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) 方法有堵塞性,会一直等到任务执行结束再执行下面的代码,
任务异步执行。
public class ForkJoinDemo {
public static class MyCallable implements Callable<String>{
String name;
public MyCallable(String name) {
super();
this.name = name;
}
@Override
public String call() throws Exception{
//模拟任务执行时间
Thread.sleep(1000);
System.out.println(name+"执行完毕");
return Thread.currentThread().getName()+"执行完毕";
}
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = null;
try {
forkJoinPool = new ForkJoinPool();
List<MyCallable> list = new ArrayList<>();
list.add(new MyCallable("任务1"));
list.add(new MyCallable("任务2"));
list.add(new MyCallable("任务3"));
System.out.println("开始提交任务" + System.currentTimeMillis());
//提交任务
List<Future<String>> futures = forkJoinPool.invokeAll(list);
System.out.println("结束提交任务" + System.currentTimeMillis());
//获取返回值
for(Future<String> future: futures){
System.out.println("返回值:" +future.get()+",时间:" + System.currentTimeMillis());
}
} catch (Exception e) {
System.out.println("main线程捕获了异常!");
e.printStackTrace();
}
}
}
开始提交任务1596988839203
任务3执行完毕
任务1执行完毕
任务2执行完毕
结束提交任务1596988840206
返回值:ForkJoinPool-1-worker-19执行完毕,时间:1596988840206
返回值:ForkJoinPool-1-worker-5执行完毕,时间:1596988840206
返回值:ForkJoinPool-1-worker-23执行完毕,时间:1596988840206
方法 public void shutdown()的使用
1.shutdown()方法执行完之后,已经提交子任务不会结束,子任务中断状态也没有改变,不会对sleep等产生影响,对于主任务也不会立即结束,而是等待主任务执行完毕。
2.shutdown()方法执行之后,提交新任务时会出错,抛异常RejectedExecutionException。
public class ForkJoinDemo {
public static class MyCallable implements Callable<String>{
String name;
public MyCallable(String name) {
super();
this.name = name;
}
@Override
public String call() throws Exception{
//模拟任务执行时间
Thread.sleep(1000);
System.out.println(name+"执行完毕");
return Thread.currentThread().getName()+"执行完毕";
}
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = null;
try {
forkJoinPool = new ForkJoinPool();
Future<String> future = forkJoinPool.submit(new MyCallable("任务1"));
//执行shutdown()中断
forkJoinPool.shutdown();
//执行下面的代码会抛异常RejectedExecutionException
//Future<String> futureA = forkJoinPool.submit(new MyCallable("任务1"));
System.out.println("返回值:" +future.get()+",时间:" + System.currentTimeMillis());
} catch (Exception e) {
System.out.println("main线程捕获了异常!");
e.printStackTrace();
}
}
}
任务1执行完毕
返回值:ForkJoinPool-1-worker-19执行完毕,时间:1596989475097
子任务MyCallable中有sleep,但是没有抛异常,说明shutdown()方法,没有更改其中断状态isInterrupted为false。主任务也正常结束。但是提交新任务会抛异常。
方法 public List<*Runnable> shutdownNow() 的使用
1.执行shutdownNow()方法,会将子任务的中断状态置为true,需要配合(Thread.currentThread() .isinterrupted() == true)使用,否则会正常执行结束。
2.主任务会正常执行结束,但是不能再提交任务,因为池已经关闭,会抛出RejectedExecutionException异常。
3.与shutdown()不同,执行shutdownNow()方法之后,主任务调用get()方法尝试获取返回值时会出错抛异常CancellationException。
方法 isTerminating()和 isTerminated()的使用
1.关于shutdown()方法:
1.执行完shutdown()之后,isTerminating()方法永远返回false,执行之前也返回false。当子任务执行结束后isTerminated()方法会返回true
示例
public class ForkJoinDemo {
public static class MyCallable implements Callable<String>{
String name;
public MyCallable(String name) {
super();
this.name = name;
}
@Override
public String call() throws Exception{
//模拟任务执行时间
for(int i = 0; i< Integer.MAX_VALUE/100;i++){
Math.random();
}
System.out.println(name+"执行完毕");
return Thread.currentThread().getName()+"执行完毕";
}
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = null;
try {
forkJoinPool = new ForkJoinPool();
Future<String> future = forkJoinPool.submit(new MyCallable("任务1"));
//执行shutdown()中断
System.out.println(forkJoinPool.isTerminating());
forkJoinPool.shutdown();
System.out.println(forkJoinPool.isTerminating());
Thread.sleep(2000);
System.out.println(forkJoinPool.isTerminating());
System.out.println(forkJoinPool.isTerminated());
} catch (Exception e) {
System.out.println("main线程捕获了异常!");
e.printStackTrace();
}
}
}
false
false
任务1执行完毕
false
true
1.关于shutdownNow()方法:
1.执行完shutdownNow()之后,isTerminating()方法返回true,执行之前返回false,当子任务执行结束后isTerminated()方法会返回true,isTerminating返回false.
示例
public class ForkJoinDemo {
public static class MyCallable implements Callable<String>{
String name;
public MyCallable(String name) {
super();
this.name = name;
}
@Override
public String call() throws Exception{
//模拟任务执行时间
for(int i = 0; i< Integer.MAX_VALUE/100;i++){
Math.random();
}
System.out.println(name+"执行完毕");
return Thread.currentThread().getName()+"执行完毕";
}
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = null;
try {
forkJoinPool = new ForkJoinPool();
Future<String> future = forkJoinPool.submit(new MyCallable("任务1"));
//执行shutdown()中断
System.out.println(forkJoinPool.isTerminating());
forkJoinPool.shutdownNow();
System.out.println(forkJoinPool.isTerminating());
Thread.sleep(2000);
System.out.println(forkJoinPool.isTerminating());
System.out.println(forkJoinPool.isTerminated());
} catch (Exception e) {
System.out.println("main线程捕获了异常!");
e.printStackTrace();
}
}
}
false
true
任务1执行完毕
false
true
方法public boolean isShutdown()的使用
执行了shutdown()或者shutdown()方法,isShutdown()会返回true。
方法public boolean awaitTermination(long timeout, TimeUnit unit)的使用
方法 awaitTermination(long timeout, TimeUnit unit)的作用是等待池被销毁的最长时间,具有堵塞特性,需要配合shutdown()或者shutdownNow()使用,如果线程池销毁返回true,否则返回false.
示例
public class ForkJoinDemo {
public static class MyCallable implements Callable<String>{
String name;
public MyCallable(String name) {
super();
this.name = name;
}
@Override
public String call() throws Exception{
//模拟任务执行时间
Thread.sleep(2000);
System.out.println(name+"执行完毕");
return Thread.currentThread().getName()+"执行完毕";
}
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = null;
try {
forkJoinPool = new ForkJoinPool();
Future<String> future = forkJoinPool.submit(new MyCallable("任务1"));
//执行shutdown()中断
forkJoinPool.shutdown();
System.out.println("开始等待池结束"+System.currentTimeMillis());
//子任务2s之后结束
System.out.println(forkJoinPool.awaitTermination(3000,TimeUnit.SECONDS));
System.out.println("池已经结束"+System.currentTimeMillis());
} catch (Exception e) {
System.out.println("main线程捕获了异常!");
e.printStackTrace();
}
}
}
开始等待池结束1596991728529
任务1执行完毕
true
池已经结束1596991730529
awaitTermination(3000,TimeUnit.SECONDS)最毒堵塞3s,堵塞2s之后,子任务结束,池关闭,awaitTermination不在等待返回true表示池结束。
方法 execute(task),submit(task),以及 invoke(task)区别
方法 execute(task),submit(task),以及 invoke(task)都可以在异步队列中执行任务,需要注意的是,方法 invoke()是阻塞的,而它们在使用上的区别其实很简单, execute(task)只执行任务,没有返回值,而 submit(task)方法有返回值,返回值类型是 ForkJoinTask ,想取得返回值时,需要使用 ForkJoinTask 对象的 get()方法,而 invoke(task)和 submit(task)方法
一样都具有返回值的功能,区别就是 invoke(task)方法直接将返回值进行返回,而不是通过ForkJoinTask 对象的 get()方法。
监视 pool 池的状态
类提供了若干个方法来监视任务池的状态:
★方法 getParallelism ():获得并行的数量,与 CPU 的内核数有关;
★方法 getPoolSize ():获得任务池的大小;
★方法getQueuedSubmissionCount():取得已经提交但尚未被执行的任务数
★方法 basQueuedSubmissions():判断队列中是否有未执行的任务;
★方法 getActiveThreadCount():获得活动的线程个数;
★方法 getQueuedTaskCount():获得任务的总个数;
★方法 getStealCount():获得偷窃的任务个数;
★方法 getRunningThreadCount():获得正在运行并且不在阻塞状态下的线程个数;
★方法 isQuiescent():判断任务池是否是静止未执行任务的状态
示例
public class ForkJoinDemo {
public static class MyRecursiveTask extends RecursiveTask<Integer>{
private int beginPosition;
private int endPosition;
public MyRecursiveTask(int beginPosition, int endPosition) {
this.beginPosition = beginPosition;
this.endPosition = endPosition;
}
@Override
protected Integer compute() {
System.out.println("beginPosition----"+beginPosition+"endPosition----"+endPosition);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return beginPosition;
}
}
public static void main(String[] args) {
try {
ForkJoinPool forkJoinPool = new ForkJoinPool(30);
for (int i = 0;i < 50; i++){
forkJoinPool.submit(new MyRecursiveTask(1,i+1));
}
Thread.sleep(800);
System.out.println("并行数:"+forkJoinPool.getParallelism()+",池大小"+forkJoinPool.getPoolSize()+",已经提交未执行任务数"+forkJoinPool.getQueuedSubmissionCount()
+",是否有未执行任务数"+forkJoinPool.hasQueuedSubmissions()+",当前活动的线程数"+forkJoinPool.getActiveThreadCount()+",任务的总数量"+forkJoinPool.getQueuedTaskCount()
+",偷窃的任务个数"+forkJoinPool.getStealCount()+",正在运行不堵塞的线程数"+forkJoinPool.getRunningThreadCount()+",当前任务池是否静止未执行任务"+forkJoinPool.isQuiescent());
forkJoinPool.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
beginPosition----1endPosition----1
beginPosition----1endPosition----2
beginPosition----1endPosition----3
beginPosition----1endPosition----4
beginPosition----1endPosition----5
beginPosition----1endPosition----7
beginPosition----1endPosition----6
beginPosition----1endPosition----8
beginPosition----1endPosition----9
beginPosition----1endPosition----10
beginPosition----1endPosition----11
beginPosition----1endPosition----12
beginPosition----1endPosition----13
beginPosition----1endPosition----14
beginPosition----1endPosition----15
beginPosition----1endPosition----16
beginPosition----1endPosition----17
beginPosition----1endPosition----18
beginPosition----1endPosition----19
beginPosition----1endPosition----20
beginPosition----1endPosition----21
beginPosition----1endPosition----22
beginPosition----1endPosition----23
beginPosition----1endPosition----24
beginPosition----1endPosition----25
beginPosition----1endPosition----26
beginPosition----1endPosition----27
beginPosition----1endPosition----28
beginPosition----1endPosition----29
beginPosition----1endPosition----30
并行数:30,池大小30,已经提交未执行任务数20,是否有未执行任务数true,当前活动的线程数30,
任务的总数量0,偷窃的任务个数0,正在运行不堵塞的线程数0,当前任务池是否静止未执行任务false
类ForkJoinTask对异常的处理
方法 isCompletedAbnormally()判断任务是否出现异常,方法 isCompletedNormally()断任务是否正常执行完毕,方法 getException()返回报错异常。
示例
public class ForkJoinDemo {
public static class MyRecursiveTask extends RecursiveTask<Integer>{
private int beginPosition;
private int endPosition;
public MyRecursiveTask(int beginPosition, int endPosition) {
this.beginPosition = beginPosition;
this.endPosition = endPosition;
}
@Override
protected Integer compute() {
System.out.println("beginPosition----"+beginPosition+"endPosition----"+endPosition);
try {
Thread.sleep(1000);
Integer.parseInt("A");
} catch (InterruptedException e) {
e.printStackTrace();
}catch (NumberFormatException e){
throw e;
}
return beginPosition;
}
}
public static void main(String[] args) {
try {
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask task = forkJoinPool.submit(new MyRecursiveTask(1,10));
//判断异常
System.out.println("子任务是否有异常抛出" + task.isCompletedAbnormally()+",子任务是否正常结束" + task.isCompletedNormally());
Thread.sleep(1500);
System.out.println("子任务是否有异常抛出" + task.isCompletedAbnormally()+",子任务是否正常结束" + task.isCompletedNormally());
//获取异常
System.out.println(task.getException());
forkJoinPool.shutdown();
} catch (Exception e) {
System.out.println("main捕获到异常!");
e.printStackTrace();
}
}
}
子任务是否有异常抛出false,子任务是否正常结束false
beginPosition----1endPosition----10
子任务是否有异常抛出true,子任务是否正常结束false
java.lang.NumberFormatException
总结
介绍了 Fork-Join 分治编程类主要的 API 需要细化掌握 ForkJoinTask常用子类的 fork 分解算法,虽然分治编程可以有效地利用 CPU 资源,但不要为了分治编程而分治,应该结合具体的业务场景来进行使用。