为了充分利用多个 CPU、多核CPU的性能优势,可以将一个任务拆分成多个小任务
,把每个小任务
放到多个处理器上并行执行
Fork/Join
框架: 在必要的情况下,将一个大任务,进行拆分(fork
) 成若干个小任务(拆到不可再拆),再将一个一个的小任务运算的结果进行join
汇总。
ForkJoinPool
是ExecutorService
的实现类,是一种特殊的线程池。
API | 描述 |
---|---|
ForkJoinPool(int parallelism) | 创建一个包含 parallelism个并行线程 的 ForkJoinPool |
ForkJoinPoll() | 以Runtime.getRuntime().availableProcessors() 方法的返回值作为Parallelism参数创建ForkJoinPool() |
java 8 进一步扩展了ForkJoinPool的功能,为ForkJoinPool增加了通用池功能。
API | 描述 |
---|---|
ForkJoinPool commonPool() |
返回一个通用池,通用池的运行状态不会受shutdown() 或者 shutdownNow() 方法的影响。 如果直接使用 System.exit(0),则会受影响 |
int getCommonPoolParallelism() |
该方法返回通用池的并行级别 |
创建了 ForkJoinPool
实例,就可以调用 ForkJoinPool
的 submit(ForkJoinTask task)
或者 invoke(ForkJoinTask task)
方法来执行指定的任务。 其中 ForkJoinTask
代表一个可以并行、合并的任务。ForkJoinTask
是一个抽象类,它有两个抽象子类:RecursiveAction
(没有返回值) 和 RecursiveTask
(有返回值)。
案例一
打印0-299,将大任务拆分成小任务,并交给 ForkJoinPoll 去执行。
使用 RecursiveAction
类,没有返回值。
package test;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;
class PrintTask extends RecursiveAction {
private static final int LENGTH = 50;
private int start;
private int end;
public PrintTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if ((end - start) < LENGTH) {
for (int i = start; i < end; i++) {
System.out.println(Thread.currentThread().getName() + " ---" + i);
}
} else {
int middle = (start + end) / 2;
PrintTask low = new PrintTask(start, middle);
PrintTask high = new PrintTask(middle, end);
//分两个任务。
low.fork();
high.fork();
}
}
}
public class TestForkJoinPool {
public static void main(String[] args) throws InterruptedException {
ForkJoinPool forkJoinPool = new ForkJoinPool();
PrintTask task = new PrintTask(0,300); //打印 0-299,并不是连续的。
forkJoinPool.submit(task);
System.out.println( "线程数:" + forkJoinPool.getActiveThreadCount()); //1
System.out.println("并行数量:" + forkJoinPool.getParallelism());//4 并行数量
System.out.println("可以使用的处理器" + Runtime.getRuntime().availableProcessors());//4
forkJoinPool.awaitTermination(2, TimeUnit.SECONDS);
forkJoinPool.shutdown();
}
}
案例二
将任务进行分解,并调用分解后的任务的fork()方法使它们并行执行。RecursiveTask
具有返回值。
package test;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
class Sum extends RecursiveTask {
private int arr[];
private int start;
private int end;
private static final int THRESHOLD = 10;
public Sum(int[] arr, int start, int end) {
this.arr = arr;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if(end - start < THRESHOLD) {
int sum = 0;
for(int i = start; i < end; i++) {
sum += arr[i];
}
return sum;
}else {
int middle = (start + end) / 2;
Sum lowSum = new Sum(arr,start,middle);
Sum highSum = new Sum(arr,middle,end);
//分成两个任务
lowSum.fork();
highSum.fork();
//把两个任务的结果累加合并在一起
return lowSum.join() + highSum.join();
}
}
}
public class TestRecursiveTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
int[] arr = new int[100];
Random random = new Random();
int total = 0;
for(int i = 0; i < arr.length; i++) {
int temp = random.nextInt(20);
total += (arr[i] = temp);
}
System.out.println(total);
ForkJoinPool pool = new ForkJoinPool();
Future future = pool.submit(new Sum(arr,0,arr.length));
System.out.println(future.get());
}
}
案例三
多个实现方式的性能比较
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
import org.junit.Test;
public class TestForkJoinPool {
public static void main(String[] args) {
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask task = new ForkJoinSumCalculate(0L, 50000000000L);
Long sum = pool.invoke(task);
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//166-1996-10590
}
@Test
public void test1(){
Instant start = Instant.now();
long sum = 0L;
for (long i = 0L; i <= 50000000000L; i++) {
sum += i;
}
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//35-3142-15704
}
//java8 新特性
@Test
public void test2(){
Instant start = Instant.now();
Long sum = LongStream.rangeClosed(0L, 50000000000L)
.parallel()
.reduce(0L, Long::sum);
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//1536-8118
}
}
class ForkJoinSumCalculate extends RecursiveTask{
/**
*
*/
private static final long serialVersionUID = -259195479995561737L;
private long start;
private long end;
private static final long THURSHOLD = 10000L; //临界值
public ForkJoinSumCalculate(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if(length <= THURSHOLD){
long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
}else{
long middle = (start + end) / 2;
ForkJoinSumCalculate left = new ForkJoinSumCalculate(start, middle);
left.fork(); //进行拆分,同时压入线程队列
ForkJoinSumCalculate right = new ForkJoinSumCalculate(middle+1, end);
right.fork(); //
return left.join() + right.join();
}
}
}
在使用ForkJoin
时需要注意: 如果任务的划分层次很深,一直得不到返回,那么可能出现两种情况:第一,系统内的线程数量越积越多,导致性能严重下降;第二,函数的调用层次变的很深,最终导致栈溢出。