Fork-Join
Fork-Join体现了 分而治之 的设计思想
- 分而治之:将一个大问题,分割成若干个相同的小问题,且各个小问题之间没有关联
原理:
- 在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务,完成每个小任务之后,再将这若干个小任务的结果进行合并(join)汇总
示意图:
工作密取:
- 所谓的工作密取,举例来说就是:假设当前大任务被拆分成了50个小任务,但是此时只有5个线程可以用来执行任务,那么也就是每个线程需要处理10个任务,那么当某一个线程较快的完成了自身的10个任务之后,它就会去其他某一个未执行完毕的线程处拿到还未处理的任务,处理完成后,将任务结果放回到对应的获取的那个线程处
工作示意图
- 工作密取可以充分的利用线程的利用率,从而加快执行速度
Fork-Join标准范式
示意图:
-
如果我们需要使用Fork-Join框架,必须首先创建一个Fork-Join任务。它需要提供在任务中执行 fork 和 Join操作机制,通常我们继承
ForkJoinTask
的子类:RecursiveTask<T>
:用于存在返回值的任务RecursiveAction
:用于没有返回值的任务
-
Task任务需要通过
ForkJoinPool
来执行,使用submit
或者invoke
来提交,两者的区别为:submit
:是异步执行,调用之后对应线程可以继续执行下面的程序,而不需要等待任务完成invoke
:是同步执行,调用之后需要等待任务完成后才能执行后面的程序
-
join
和get
方法用来获取任务执行结束后返回的结果 -
在我们自己实现的
compute
方法中,首先需要判断任务是否足够小,如果足够小,则直接执行任务;如果判断任务还不够小,就必须继续拆分,分割成两个子任务,每个子任务在调用 **invokeAll()
**方法时就又会进入 **compute
**方法;使用 **join
**方法会等待子任务执行完得到其结果。 -
使用Fork-Join进行相关功能实现的时候,其实是将工作内容分成两个部份:
- Fork,即解析任务,过滤到足够小进行分步执行;
- Join,即将小任务执行完毕的结果整合。最终完成完整功能的实现。
代码实例:
/* 使用RecursiveTask计算一个大数组,并得到其最终计算值 */
package demo3;
import java.util.Random;
class UserArray {
//数组长度
public static final int ARRAY_LENGTH = 4000;
public static int[] makeArray() {
//new一个随机数发生器
Random r = new Random();
int[] result = new int[ARRAY_LENGTH];
for(int i=0;i<ARRAY_LENGTH;i++){
//用随机数填充数组
result[i] = r.nextInt(ARRAY_LENGTH*3);
}
return result;
}
}
package demo3;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
class SumArray extends RecursiveTask<Integer> {
public int[] src;
public int fromIndex;
public int toIndex;
public static final int STANDED = UserArray.ARRAY_LENGTH/10;
public SumArray(int[] array, int start, int end) {
src = array;
fromIndex = start;
toIndex = end;
}
@Override
protected Integer compute() {
//首先判断当前任务是否足够小
if ((toIndex - fromIndex) < STANDED) {
//任务已经足够小,直接进行计算
int result = 0;
for(int i = fromIndex; i < toIndex; i++) {
result += src[i];
}
return result;
} else {
//任务还不够小,需要进一步拆分
//此处采取二分法,取中间值进行拆分
int midIndex = (fromIndex + toIndex) / 2;
//将这个任务分割成两个子任务,即创建两个子任务
SumArray leftSumTask = new SumArray(src, fromIndex, midIndex);
SumArray rightSumTask = new SumArray(src, (midIndex + 1), toIndex);
//创建了两个子任务后,将子任务通过invokeAll进行提交
invokeAll(leftSumTask, rightSumTask);
//提交完之后,通过join方法获取子任务的返回值
return leftSumTask.join() + rightSumTask.join();
}
}
public static void main(String[] args) {
//创建一个需要计算的数组
int[] Array = UserArray.makeArray();
//创建ForkJoinPool
ForkJoinPool pool = new ForkJoinPool();
//创建fork-join任务
SumArray sumTask = new SumArray(Array, 0, Array.length - 1);
//记录一下开始时间
long start = System.currentTimeMillis();
//将创建的fork-join任务提交到ForkJoinPool中进行执行
pool.invoke(sumTask);
//通过join方法获取fork-join任务的执行结果
int result = sumTask.join();
//输出结果和花费时间
System.out.println("sum result = " + result + ", spend time = " + (System.currentTimeMillis() - start));
}
}
/* 使用RecursiveTask进行一个大数组的排序,并得到其最终排序后的数组 */
class SortArray extends RecursiveTask<int[]> {
public int[] src;
public int fromIndex, toIndex;
public static final int STAND = UserArray.ARRAY_LENGTH/10;
public SortArray(int[] array) {
this.src = array;
fromIndex = 0;
toIndex = src.length - 1;
}
@Override
protected int[] compute() {
//进行各个子表的排序
//首先进行子表大小的判断,fork中进行子表排序
if ((toIndex - fromIndex) < STAND) {
int i, j, temp;
for (i = fromIndex; i < toIndex; i++) {
for (j = i+1; j<= toIndex; j++) {
if (src[i] > src[j]) {
temp = src[i];
src[i] = src[j];
src[j] = temp;
}
}
}
return src;
} else {
//子表大小不满足要求,需要继续拆分
int midIndex = (fromIndex + toIndex)/2;
SortArray left = new SortArray(Arrays.copyOfRange(src, fromIndex, midIndex));
SortArray right = new SortArray(Arrays.copyOfRange(src, midIndex+1, toIndex));
//拆分的左右子表再提交
SortArray.invokeAll(left, right);
//得到每个子表的排序,得到有序表
int[] resLeft = left.join();
int[] resRight = right.join();
//将有序子表进行合并
int[] result = new int[resLeft.length + resRight.length];
for(int index = 0, i = 0, j = 0; index < result.length; index++) {
if (i >= resLeft.length) {
//如果i大于左子表的长度,说明左子表存储完成,依照顺序存储右子表即可
result[index] = resRight[j++];
} else if (j >= resRight.length) {
//如果j大于右子表的长度,说明右子表存储完成,依照顺序存储左子表即可
result[index] = resLeft[i++];
} else if (resLeft[i] > resRight[j]) {
//如果左子表的值大于右子表的值,那么将右子表的值存入最后的result数组中
result[index] = resRight[j++];
}else if (resLeft[i] <= resRight[j]) {
//如果右子表的值大于左子表的值,那么将左子表的值存入最后的result数组中
result[index] = resLeft[i++];
}
}
return result;
}
}
public static void main(String[] args) {
//新建排序数组
int[] array = UserArray.makeArray();
//创建ForkJoinPool
ForkJoinPool pool = new ForkJoinPool();
//新建task
SortArray sortTask = new SortArray(array);
//将任务放进pool里执行
pool.invoke(sortTask);
//得到最终的排序后的数组
int[] res = sortTask.join();
for (int i = 0; i < res.length; i++) {
System.out.println("array[ " + i + " ] = " + res[i] + "\n");
}
}
}
- 如果我们使用单线程执行累加,会发现只针对该示例,单线程执行速度反而更快,这个原因是:使用Fork-Join框架,是使用递归及多线程的方式进行运算,而递归会涉及出栈入栈动作,多线程的存在则会涉及上下文切换的动作,而这都是耗时的操作,所以在数据量不大,且任务不耗时的场景下,会出现使用Fork-Join不如单线程运行的快的现象,这也说明,使用Fork-Join不代表着运行一定快;但只要当任务是耗时的,或者数据量很大的场景,使用Fork-Join运行效率和速度就能够得以体现!
CountDownLatch
CountDownLatch作用:
-
CountDownLatch 这个类能够使一个线程等待其他线程完成各自的工作后再执行
-
CountDownLatch 本质上就是通过一个计数器来实现的,计数器的初始值为初始任务的数量,每当完成一个任务,就可以调用
CountDownLatch.countDown()
去进行计数器减一,当计数器为0时,就表示所有的任务都已经完成了,那么在闭锁上等待CountDownLatch.await()
的线程就可以恢复执行
CountDownLatch的执行示意图:
从这幅图中,我们可以获知三个信息:
1. CNT的数值和线程数可以不相等,CNT的数值可以远远大于线程数
2. 同一个线程可以多次执行`CountDownLatch.countDown()`去进行计数器递减的操作
3. 可以多个线程调用`CountDownLatch.await()`方法进行等待
使用CountDownLatch时,无需考虑线程安全问题,因为CountDownLatch内部使用了AQS,具体可见之后的AQS介绍文章
代码示例
public class MyClass {
public static CountDownLatch latch;
static class MyThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
System.out.println("this Thread:" + Thread.currentThread().getName() + ",latch num = " + latch.getCount());
}
}
public static void main(String[] args) throws InterruptedException {
latch = new CountDownLatch(3);
Thread thread1 = new Thread(new MyThread());
Thread thread2 = new Thread(new MyThread());
thread1.start();
thread2.start();
System.out.println("This is Main Thread, latch count num = " + latch.getCount());
latch.countDown();
latch.await();
System.out.println("main thread end............");
}
}