一、Fork-Join
什么是分而治之?
规模为N的问题,N<阈值,直接解决,N>阈值,将N分解为K个小规模子问题,子问题互相对立,与原问题形式相同,将子问题的解合并得到原问题的解。
Fork-Join使用两个类来完成以上两件事情:
ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork()和join()操作的机制,通常情况下我们不需要直接继承ForkJoinTask类,而只需要继承它的子类,Fork/Join框架提供了以下两个子类:
RecursiveAction:用于没有返回结果的任务。
RecursiveTask :用于有返回结果的任务。
ForkJoinPool :ForkJoinTask需要通过ForkJoinPool来执行,任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。
public class FindDirsFiles extends RecursiveAction{
private File path;//当前任务需要搜寻的目录
public FindDirsFiles(File path) {
this.path = path;
}
public static void main(String [] args){
try {
// 用一个 ForkJoinPool 实例调度总任务
ForkJoinPool pool = new ForkJoinPool();
FindDirsFiles task = new FindDirsFiles(new File("F:/"));
pool.execute(task);//异步调用
System.out.println("Task is Running......");
task.join();//阻塞的方法
System.out.println("Task end");
} catch (Exception e) {
e.printStackTrace();
}
}
//业务方法
@Override
protected void compute() {
List<FindDirsFiles> subTasks = new ArrayList<>();
File[] files = path.listFiles();
if(files!=null) {
for(File file:files) {
if(file.isDirectory()) {
subTasks.add(new FindDirsFiles(file));
}else {
//遇到文件,检查
if(file.getAbsolutePath().endsWith("txt")) {
System.out.println("文件:"+file.getAbsolutePath());
}
}
}
if(!subTasks.isEmpty()) {
for(FindDirsFiles subTask:invokeAll(subTasks)) {
subTask.join();//等待子任务执行完成
}
}
}
}
}
//单线程递归读取某文件夹下的txt文件
public static void readFile(File file) {
File[] files = null;
if (file != null) {
files = file.listFiles();
}
if (files != null) {
for (File f : files) {
if (f.isDirectory()) {
readFile(f);
} else {
if (f.getAbsolutePath().endsWith("txt"))
System.out.println(f.getAbsolutePath());
}
}
}
}
二、CountDownLatch
CountDownLatch是一个同步辅助类,它作用是让一个或多个线程等待其他的线程完成工作以后再执行,加强版Thread.join()。
CountDownLatch核心方法:
- await 使调用该方法的线程处于等待状态,其一般是主线程调用。
- countDown 用于使计数器减一,其一般是执行任务的线程调用
public class CountDownLatchDemo {
private static final int NUM=5;
//初始化
static CountDownLatch countDownLatch=new CountDownLatch(NUM);
public static void main(String[] args) throws Exception{
System.out.println("开始执行");
long start=System.currentTimeMillis();
for(int i=0;i<NUM;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+":执行业务");
countDownLatch.countDown();//线程执行完计数器减一
}).start();
}
//主线程等所有线程完成工作才继续执行后面的代码,当计算器减到0阻塞结束
countDownLatch.await();
long end=System.currentTimeMillis();
System.out.println("所有线程执行结束,耗时:"+(end-start)+"毫秒");
}
}
三、CyclicBarrier
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
public class CyclicBarrierDemo {
private static final int NUM=5;
private static CyclicBarrier cyclicBarrier=new CyclicBarrier(NUM);
//所有线程开始才继续向下继续执行
public static void main(String[] args) {
for(int i=0;i<NUM;i++){
new Thread(()->{
System.out.println("开始");
try {
cyclicBarrier.await();
System.out.println("开始执行");
Thread.sleep(10000);
System.out.println("执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
CountDownLatch和CyclicBarrier区别
- CountDownLatch放行由第三者控制,CyclicBarrier放行由一组线程本身控制
- CountDownLatch放行条件>=线程数,CyclicBarrier放行条件=线程数
- CountDownLatch是不可重置的,所以无法重用;而CyclicBarrier则没有这种限制,可以重用。