ForkJoin是由JDK1.7后提供多线并发处理框架, ForkJoin的框架的基本思想是分而治之。使用ForkJoin将相同的计算任务通过多线程的进行执行, 从而能提高数据的计算速度。在google的中的大数据处理框架mapreduce就通过类似ForkJoin的思想, 通过多线程提高大数据的处理。
使用ForkJoin框架, 需要创建一个ForkJoin的任务。因为ForkJoin框架为我们提供了RecursiveAction和RecursiveTask。我们只需要继承ForkJoin为我们提供的抽象类的其中一个并且实现compute方法。
分而治之就是将一个复杂的计算, 按照设定的阈值进行分解成多个计算, 然后将各个计算结果进行汇总。相应的ForkJoin将复杂的计算当做一个任务, 而分解的多个计算则是当做一个子任务。
Task要通过ForkJoinPool来执行, 的子任务也会添加到当前工作线程的双端队列中, 进入队列的头部。当一个工作线程中没有任务时, 会从其他工作线程的队列尾部获取一个任务(工作窃取)。
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建MyTask对象
MyTask myTask = new MyTask(0, 100);
//创建分支合并池对象
ForkJoinPool forkJoinPool = new ForkJoinPool();
//获取合并之后非结果
ForkJoinTask<Integer> submit = forkJoinPool.submit(myTask);
Integer result = submit.get();
System.out.println(result);
//关闭线程池对象
forkJoinPool.shutdown();
}
工作窃取(work-stealing)
任务进行分解成多个子任务的时候,每个子任务的处理时间都不一样。
例如分别有子任务A和B。如果子任务A的1ms的时候已经执行,子任务B还在执行。那么如果子任务A的线程等待子任务B完毕后在进行汇总,那么子任务A线程就会在浪费执行时间,最终的执行时间就以最耗时的子任务为准。
而如果子任务A执行完毕后,处理子任务B的任务,并且执行完毕后将任务归还给子任务B。这样就可以提高执行效率,这就是工作窃取。
使用二分法将100不断拆分, 直到每个线程的任务都相对轻松为止, 直接看代码:
//写这段代码的人肯定没听说过高斯的故事
public class ForkJoinDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建MyTask对象
MyTask myTask = new MyTask(0, 100);
//创建分支合并池对象
ForkJoinPool forkJoinPool = new ForkJoinPool();
//获取合并之后非结果
ForkJoinTask<Integer> submit = forkJoinPool.submit(myTask);
Integer result = submit.get();
System.out.println(result);
//关闭线程池对象
forkJoinPool.shutdown();
}
}
class MyTask extends RecursiveTask<Integer> {
//拆分值不能超过10, 防止线程过度创建
private static final int VALUE = 10;
private int begin;
private int end;
private int result = 0;
public MyTask(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
protected Integer compute() {
if ((end - begin) <= VALUE) {
for (int i = begin; i <= end; i++) {
result += i;
}
} else {
int middle = (begin + end) / 2;
MyTask myTask1 = new MyTask(begin, middle);
MyTask myTask2 = new MyTask(middle + 1, end);
myTask1.fork();
myTask2.fork();
result = result + myTask1.join() + myTask2.join();
}
return result;
}
}
八皇后问题是一个古老而又著名的问题, 是学习回溯算法的一个经典案例。
在8×8格的国际象棋上摆放八个皇后, 使其不能互相攻击, 即任意两个皇后都不能处于同一行、同一列或同一斜线上, 问一共有多少种摆法。
我们这里使用Fork Join框架并行解决问题, 加快处理速度。
上代码:
//0为棋盘, 8为皇后, 1为皇后攻击位
public class ForkJoinDemo2 {
public static void main(String[] args) throws InterruptedException {
int[][] chessboard = new int[8][8];
ForkJoinPool forkJoinPool = new ForkJoinPool();
forkJoinPool.submit(new EightQueen(0, chessboard));
//等待任务完成
forkJoinPool.awaitTermination(2, TimeUnit.SECONDS);
forkJoinPool.shutdown();
}
}
class EightQueen extends RecursiveAction {
private static int count = 0;
int row;
int[][] chessboard;
public EightQueen(int row, int[][] chessboard){
this.row = row;
this.chessboard = chessboard;
}
//打印棋盘, 这里一定要加把锁, 不然打印会乱套
private synchronized static void printChessboard(int[][] chessboard) {
System.out.println("----第" + (++count) + "种解法----");
for (int row = 0; row < chessboard.length; row++) {
for (int col = 0; col < chessboard[row].length; col++) {
if (chessboard[row][col] == 1) chessboard[row][col] = 0;//将被判定为攻击位的地方重新初始化为*
System.out.print(chessboard[row][col]);
System.out.print(' ');
}
System.out.println();
}
}
//把攻击位设置为1
private static void putAttack(int nowRow, int nowCol, int[][] chessboard) {
for (int row = 0; row < chessboard.length; row++) {
chessboard[row][nowCol] = 1;
for (int col = 0; col < chessboard[row].length; col++) {
chessboard[nowRow][col] = 1;
if (row + col == nowRow + nowCol) chessboard[row][col] = 1;
if (row - col == nowRow - nowCol) chessboard[row][col] = 1;
}
}
chessboard[nowRow][nowCol] = 8;//上面的方法会把皇后位修改成攻击位,这里需要复原成皇后位
}
@Override
protected void compute() {
if (row == chessboard.length) {//如果行数等于length,说明最后一行放置完毕,打印棋盘并退出方法
printChessboard(chessboard);
return;
}
for (int col = 0; col < chessboard[row].length; col++) {
if (chessboard[row][col] == 0) {
//创建备份, 等待分支结束后数组复原
int[][] backup = new int[chessboard.length][chessboard.length];
for (int i = 0; i < chessboard.length; i++) {
for (int j = 0; j < chessboard[i].length; j++) {
backup[i][j] = chessboard[i][j];
}
}
chessboard[row][col] = 8;
putAttack(row, col, chessboard);
//开启分支, 进入下一行
EightQueen eightQueen = new EightQueen(row + 1, chessboard);
eightQueen.fork();
//分支结束, 数组复原
chessboard = backup;
}
}
}
}
我的个人主页: www.ayu.link
本文连接: ┏ (゜ω゜)=☞