分而治之的思想不陌生了,归并排序用到,即把待排序列递归地等分成两个子序列,并排序,这样在不停递归等分,排序归并过程中,会得到若干个有序序列,因为递归过程是等分成左右序列嘛,所以最后会得到两个有序的序列,再做一次归并操作即可。外部排序中也用到,现在在多线程中也有,在多线程中分而治之思想有基于数据的分割和基于任务的分割。拿基于数据的分割来说,例如对于一个大文件的下载,使用多个线程分别下载一个大文件的不同部分,比用单线程单独下载一个大文件节省很多的时间,等下会用代码举例。
在Java中也有实现这种分工合作思想的工具:Fork/Join框架。fork()功能是创建分支线程,join()功能一样是线程等待的意思,需要等待调用join()的线程执行完成后才能继续。因为fork()和join两个方法都是ForkJoinTask类的方法,所以在一个Fork/Join’框架下,由ForkJoinTask类来表示任务。
JDK中提供个ForkJoinPool线程池来对fork()创建的分支线程做管理。这个ForkJoinPool里的线程池有一个特点,就是当线程池里面的fork()线程完成自己的手头工作后,会去主动帮助其他还未完成任务的fork()线程执行它未完成的任务。因为在实际应用中,一个线程是需要完成多个任务的,每个线程都有自己的任务队列,里面放着等待这个线程调度执行的任务,当线程T1执行完自己的所有任务后,会去查看其他线程例如T2的任务队列,假设线程T2的任务队列中有很多任务等待着处理,那么T1就会从T2的任务队列最底端开始抽取任务来帮助线程T2的执行,这种从最底端开始抽取任务来执行的行为可以尽可能避免资源竞争,帮助线程执行任务行为也能提高CPU利用率。
好了,大概知道fork()方法用来把任务创建分支,和用join()等待任务完成并返回结果。这两个方法都是ForkJoinTask类的方法,也就是说要用fork/join框架,就要使用ForkJoinTask类来实现,ForkJoinTask类有两个子类,RecursiveAction类和RecursiveTask类,想要使用Fork/Join框架的任务都应该继承自这两个类中的其中一个来实现(翻看了片文档,Fork/Join),继承自RecursiveAction类的任务表示该任务没有返回值;继承自RecursiveTask类的任务表示该任务有返回值。来看看具体实现代码:
package forkjoinframe;
import java.util.ArrayList;
import java.util.concurrent.RecursiveTask;
public class ForkJoinDownloadTask extends RecursiveTask {
private static final long serialVersionUID = 1L;
private long lowerBound; //起始字节
private long upperBound; //结束字节
private long dataSum; //(子)任务数据量
private long sum = 0;
public ForkJoinDownloadTask(long lowerBound, long upperBound) {
this.lowerBound = lowerBound;
this.upperBound = upperBound;
}
@Override
protected Long compute() {
if((upperBound-lowerBound)<10000) {
for(long i=lowerBound; i<=upperBound; i++) {
sum +=i; //模拟下载数据
}
} else {
//如果文件太大,就进行分割处理
long part = (upperBound-lowerBound)/100; //记录分割成100部分,每部分文件的大小
long startLocation = lowerBound; //initLocation记录任务的初始开始位置
//存放这100个子任务的队列
ArrayList downloadTask = new ArrayList();
//把分成的100个子任务都提交到队列里去
for(int i=0; i<100; i++) {
long endLocation = startLocation+part; //记录该子任务的下载结束位置
//特殊情况判断,如果该子任务的结束位置大于总任务的结束位置,即当前子任务是最后一个了.
if(endLocation>upperBound) {
//让子任务的结束位置等于总任务大小的结束位置.
endLocation = upperBound;
}
//实例化子任务
ForkJoinDownloadTask downloadtask = new ForkJoinDownloadTask(startLocation, endLocation);
//输出子任务的信息
downloadtask.dataSum = downloadtask.upperBound - downloadtask.lowerBound; //该子任务的下载数据量
sum+=downloadtask.dataSum; //sum保存数据总量
System.out.println("Task - "+i+": startLocation="+downloadtask.lowerBound+", "
+ "endLocation="+downloadtask.upperBound);
System.out.println("dataSum="+downloadtask.dataSum+" sum="+sum+"\r\n");
startLocation+=part; //下一个子任务的开始位置
downloadTask.add(downloadtask); //把当前子任务存入任务队列
downloadtask.fork(); //为子任务创建分支
}
//所有子任务提交到任务队列完成
for(ForkJoinDownloadTask t:downloadTask) {
t.join(); //join()等待任务,即"等待子任务下载完成"
}
}
return sum;
}
}
这段代码是模拟下载一个大文件,任务FrokJoinDownloadTask继承RecursiveTask类,表示该任务有返回值,因为我们想要在每个子任务完成任务后输出下自己“下载”了多少数据,返回自己的”下载”数据量后也可以用来统计总的完成下载数据量。
继承RecursiveTask类后,就要重写它的compute方法实现要执行的任务,在这个模拟下载任务中,首先判断下载的数据量大小,如果要下载的数据量小于10000,就直接“下载“可以了,否则大于10000,就把它分解成100个子任务来分别下载这些数据量。注意第34行创建了一个子任务队列,给每个子任务fork()时也把子任务存放到这个子任务队列里去,然后第60行我们把这子任务队列中的所有子任务都调用join()方法。最后第65行会返回sum变量,sum记录着当前下载的总数据量。
package forkjoinframe;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
public class ForkJoinFrameDemo {
public static void main(String[] args) {
ForkJoinPool forkjoinpool = new ForkJoinPool(); //ForkJoinPool线程池
ForkJoinDownloadTask task = new ForkJoinDownloadTask(0, 300000L); //实例化下载任务
ForkJoinTask taskResult = forkjoinpool.submit(task); //存放下载任务返回的结果
try {
long result = taskResult.get(); //获取每个子任务的下载结果
System.out.println("completed! sum = "+result);
} catch(InterruptedException e) {
e.printStackTrace();
} catch(ExecutionException e) {
e.printStackTrace();
}
}
}
在实例化任务和调用任务的类中,第12行我们用一个ForkJoinTask
可以看到,对于下载一个数据量为300000任务被分成了编号为0-99的100个子任务,每个任务下载数据量为3000。
看了一些文档,ForkJoinPool线程池中的线程是由ForkJoinWorkerThread类实现的,线程池存放继承ForkJoinTask子类的任务是由一个ForkJoinPool.WorkQueue双端队列来存放,以此来实现线程之间的互相帮助,也就是上面提到的,对于同一个任务的子任务线程,在某个线程完成自己的所有任务(包括该线程的任务队列中的任务)后,会去帮助其他线程完成其任务队列中的等待任务。
详细代码已上传:
https://github.com/justinzengtm/Java-Multithreading/tree/master/ForkJoinFrame
https://gitee.com/justinzeng/multithreadthread_pool/tree/master/ForkJoinFrame