ForkJoin
采用分治的思想,将一个大的任务拆分成小任务,在把每个小任务都交给一个线程去处理,如果有线程提前处理完任务,可以工作窃取也就是从其他线程的任务队列尾部里偷取任务,帮他处理。
之前简单学过ForkJion,后来有个问题一直困扰我,并发只是线程间的来回切换谁分配到时间片谁执行,在某一时候是只有一个线程在执行的,并不会节约时间,会提高CPU的使用率,但是为什么用ForkJion却比单线程执行的时间短。今天正好看到了ForkJion,又重新看了下之前的代码,终于发现了问题所在。
之前就是做的从1加到1亿,分别用单线程和ForkJion做了一下时间的对比,发现ForkJion会快一些,其实快就快在,单线程情况下数是一直累加了数越大累加运算耗时就越高,而ForkJion对任务进行了拆分所以用时短一些。
总结:
正确使用ForkJion才会提高效率,错用效率可能比单线程还低。
①如果任务不能拆分ForkJion是不能使用的
②任务量小,线程间切换会涉及上下文的切换会有额外的时间消耗,可能效率比单线程还低。
③需求不同,效率不同。比如累加这种需求就挺适合用ForkJion的,任务拆分后子任务会轻松一些,而有的需求就不合适了。
代码:
package day02.forkjoin;
/**
* @Author 小浩
* @Date 2019/11/9 22:27
* @Version 1.0
**/
public class Array {
public static int SIZE = 100000000;
public static int[] getArray(){
int arr[] = new int[SIZE];
for (int i=0;i<SIZE;i++){
arr[i]=i;
}
return arr;
}
}
package day02.forkjoin;
import java.util.concurrent.RecursiveTask;
/**
* @Author 小浩
* @Date 2019/11/9 22:34
* @Version 1.0
**/
/**
* RecursiveAction 不带返回值 RecursiveTask 带返回值
*/
public class Task extends RecursiveTask<Integer> {
private static int TASK_SIZE = Array.SIZE / 10;
private int[] arr;
private int left;
private int right;
public Task(int[] arr, int left, int right) {
this.arr = arr;
this.left = left;
this.right = right;
}
@Override
protected Integer compute() {
if (right - left <= TASK_SIZE) {
System.out.println("left:" + left + "right:" + right);
System.out.println(Thread.currentThread().getName());
int count = 0;
for (int i = left; i <= right; i++) {
count += arr[i];
}
return count;
} else {
int mid = (left + right) / 2;
Task leftTask = new Task(arr, left, mid);
Task rightTask = new Task(arr, mid + 1, right);
invokeAll(leftTask, rightTask);
return leftTask.join() + rightTask.join();
}
}
}
package day02.forkjoin;
import java.util.concurrent.*;
/**
* @Author 小浩
* @Date 2019/11/9 22:01
* @Version 1.0
**/
/**
* 采用forkjoin框架多线程处理任务
* 调用execute和submit时 异步执行可能任务处理不完线程就关闭了 此时可以用join方法 或者让主线程睡眠等待
*/
public class ForkJoinTest {
public static void main(String[] args) {
int arr[] = Array.getArray();
long begin = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
Task task = new Task(arr,0,Array.SIZE-1);
// 调用execute方法异步执行 没有返回值
// pool.execute(task);
// 调用submit方法异步执行 有返回值 调用get方法时同样会使main线程进入阻塞状态执行完才会执行下面代码 相当于同步执行
// pool.submit(task);
// 调用invoke方法 同步执行 会使main线程进入阻塞状态执行完才会执行下面代码
System.out.println( pool.invoke(task));
long end = System.currentTimeMillis();
System.out.println(end-begin);
}
}
package day02.forkjoin;
/**
* @Author 小浩
* @Date 2019/11/9 22:50
* @Version 1.0
**/
/**
* 单线程处理任务 与forkjoin框架多线程 进行比较
*/
public class Test {
public static void main(String[] args) {
int arr[] = Array.getArray();
int count = 0;
long begin = System.currentTimeMillis();
System.out.println(begin);
for (int i = 0 ;i <Array.SIZE ;i++){
count = count + arr[i];
}
long end = System.currentTimeMillis();
System.out.println(count);
System.out.println(end);
System.out.println(end-begin);
}
}
从1累加到1000万结果对比:
使用ForkJion:
单线程情况下:
从1累加到10亿结果对比:
使用ForkJion:
单线程情况下:
© 版权声明
必看吧所有博客文章均由小浩原创,转载请保留必看吧版权!
THE END
喜欢就支持以下吧
4213187作者0
很显然,值越界了但是不影响我们要测试的结果!
4213187作者0
补充一下,当异步使用ForkJionPool时,主线程关闭后ForkJionPool也会被关闭。理论上线程只要启动了,只有等所有线程执行完,进程才会关闭,后来查看ForkJionPool相关源码后发现里边的线程是守护线程。。。。。。。。。 线程池无论同步调用还是异步调用都不会出现这种问题,当然线程池不关闭是因为调用阻塞队列的take的方法,后续博客可能会发一个线程池的简单源码剖析。
4213187作者0
补充一点,ForkJionPoo默认启动的线程数是逻辑内核数,比如我的i5 6代 就是4 ,这时候基本涉及不到线程之间的切换,也就涉及不到上下文切换。