什么是Fork/Join框架?
fork/join框架是ExecutorService接口的一个实现,可以充分利用多核处理器的优势,编写出并行执行的程序,提高应用程序的性能;主要是为了处理那些可以被递归拆分的任务。
fork/join框架与其它ExecutorService的实现类相似,会给线程池中的线程分发任务,实现异步多线程处理任务,进而大大提高处理效率,不同之处在于它使用了工作窃取算法,所谓工作窃取,指的是对那些处理完自身队列中任务的线程,会从其它的任务队列中窃取任务执行;比如大批量任务使用Fork/Join处理,并且被拆分成A,B两部分进行处理,A,B各自先处理自己WorkQueue中的ForkJoinTask,假如A率先处理完自己的WorkQueue中的ForkJoinTask,那么A会从B WorkQueue中的尾部拿走一些ForkJoinTask来处理,进而提高效率。
fork/join框架提交处理任务使用的是自带的ForkJoinPool线程池
应用场景1:计算1+…+100000
package forkJoin;
import java.util.concurrent.RecursiveTask;
public class MyForkJoinTest extends RecursiveTask<Long> {
/**
*
*/
private static final long serialVersionUID = -5363777012182729166L;
// 最大容量
private static final Long MAX_CAPACITY = 10000L;
private Integer start;
private Integer end;
public MyForkJoinTest(Integer start, Integer end) {
this.start = start;
this.end = end;
}
protected Long compute() {
Long result = 0L;
if(end-start<MAX_CAPACITY){
for (int i = 1 ; i <= end-start ; i++ ){
result+=i;
}
}else{// 超过最大容量则进行分拆任务
// 计算容量中间值
int middle = (start + end)/2;
// 进行递归
MyForkJoinTest forkJoinTest1 = new MyForkJoinTest(start, middle);
MyForkJoinTest forkJoinTest2 = new MyForkJoinTest(middle + 1, end);
// 执行任务
forkJoinTest1.fork();
forkJoinTest2.fork();
// 等待任务执行并返回结果(阻塞)
result = forkJoinTest1.join() + forkJoinTest2.join();
}
return result;
}
}
package test;
import java.util.concurrent.ForkJoinPool;
import forkJoin.MyForkJoinTest;
import org.junit.Test;
public class ForkJoin {
/**
* 第一种执行方式:可以直接将任务提交给ForkJoinPool。
* ForkJoinPool.execute(ForkJoinTask task);该方法没有返回值
* ForkJoinPool.invoke(ForkJoinTask task);直接有返回值。
*/
@Test
public void testForkJoin1(){
ForkJoinPool pool = new ForkJoinPool();
MyForkJoinTest test = new MyForkJoinTest(1,100000);
Long result = pool.invoke(test);
System.out.println(result);
}
/**
* 第二种执行方式:RecursiveTask.fork()开始执行。
* RecursiveTask.join()返回执行结果。
*/
@Test
public void testForkJoin2(){
MyForkJoinTest test = new MyForkJoinTest(1,100000);
Long result = test.fork().join();
System.out.println(result);
}
}
应用2:异步执行相关代码逻辑;
例子:开发中,有获取搜索列表的接口。一般需要获取二部分数据,搜索到的结果集和已经满足搜索条件的count值;需要异步执行提高效率时,会额外开一个新线程new Runnable(()-{}),这样可能会过多的消耗系统资源。所以我们会同时使用线程池。可能是自定义的线程池,也可能是使用自带的例如:newFixedThreadPool 。我们也可以使用fork/Join实现。
// async invoke
RecursiveTask<List<DTO>> searchTask = (RecursiveTask<List<DTO>>) new RecursiveTask<List<DTO>>() {
@Override
protected List<DTO> compute() {
return service.search(param);
}
}.fork();
RecursiveTask<Integer> countTask = (RecursiveTask<Integer>) new RecursiveTask<Integer>() {
@Override
protected Integer compute() {
return service.count(param);
}
}.fork();
// 搜索结果集
List<DTO> result = searchTask.join();
// count值
Integer count = countTask.join();
注意:Fork/Join最大的优势是充分CPU多核计算效率,但也会使得CPU在处理的时,把整个CPU占满,所以,Fork/Join只适合一些离线系统,不适合在线系统应用中使用Fork/Join,如果使用的话,在Fork/Join处理时间内,系统的其他功能全部阻塞挂掉,这点要特别注意。。测试代码如下:我拆分30000个子任务,我的CPU占用达到了98%,对于在线系统这是很危险的。选择使用时该框架时要好好考虑下哦。如果仅仅是像应用2只是为了异步执行某些逻辑的场景下使用F/J是没问题的。。
package forkJoin;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class MyForkJoinTest extends RecursiveTask<Long> {
/**
*
*/
private static final long serialVersionUID = -5363777012182729166L;
// 最大容量
private static final Long MAX_CAPACITY = 10L;
private Integer start;
private Integer end;
public MyForkJoinTest(Integer start, Integer end) {
this.start = start;
this.end = end;
}
protected Long compute() {
Long result = 0L;
if(end-start<MAX_CAPACITY){
for (int i = 1 ; i <= end-start ; i++ ){
System.out.println(Thread.currentThread().getName()+"的i值:"+i);
result+=i;
}
}else{// 超过最大容量则进行分拆任务
// 计算容量中间值
int middle = (start + end)/2;
// 进行递归
MyForkJoinTest forkJoinTest1 = new MyForkJoinTest(start, middle);
MyForkJoinTest forkJoinTest2 = new MyForkJoinTest(middle + 1, end);
// 执行任务
forkJoinTest1.fork();
forkJoinTest2.fork();
// 等待任务执行并返回结果(阻塞)
result = forkJoinTest1.join() + forkJoinTest2.join();
}
return result;
}
public static void main(String [] args) throws Exception{
System.out.println(Runtime.getRuntime().availableProcessors());
ForkJoinPool fjp = new ForkJoinPool();
MyForkJoinTest myForkJoinTest = new MyForkJoinTest(0,300000);
fjp.invoke(myForkJoinTest);
}
}