线程基础、线程之间的共享和协作
(目前会将一些概念简单描述,一些重点的点会详细描述)
学习目标:多线程的并发工具类(1)
用途,概念:
ForkJoinPool的优势在于,利用多核CPU,将一个任务,拆分成多个小任务 ,将这些小任务分配到多个处理器上并行执行;当小任务都执行完成之后,再将结果进行合并汇总。每个小任务间都没有关联,与原任务的形式相同。体现了“分而治之”的概念。任务递归分配成若干个小任务 -- 并行求值 -- 结果合并。
1、ForkJoinPool
Java7提供了ForkJoinPool来支持将一个任务拆分成多个小任务进行并行计算,再将多个“小任务”的结果进行join汇总。
2、invoke、invokeAll
执行指定的任务,等待任务,完成任务返回结果。
3、递归算法
在继承RecurisiveTask(有返回结果),RecurisiveAction(无返回结果)类时,通过递归的方式,来将任务拆分成一个一个的小任务,通过invokeAll()方法来调度子任务,等待任务完成返回结果。注意,只有在ForkJoinPool执行计算过程中调用它们。
先来看一个例子:
通过计算10000000组整型数的求和,用两种方式:普通单线程算法求和,利用ForkJoinPool多线程递归的求和。
定义一个随机产生10000000的整型数组的类
public class MakeArray {
// 设置一个final类型的整型数,表示要操作的数量
public static final int MAX_ARRAY = 1000000;
// 定义一个方法,随机产生MAX_ARRAY个数
public static int[] makeArray() {
// 设置一个Random类,作为随机生产数作用
Random rand = new Random();
// 定义一个整型数组,能存储MAX_ARRAY个数
int[] array = new int[MAX_ARRAY];
for (int i = 0; i < MAX_ARRAY; i++) {
// 往array数组里面随机添加一个数
array[i] = rand.nextInt(MAX_ARRAY * 2);
}
// 返回
return array;
}
}
定义一个普通的单线程求和类
public class SumNormal {
// 普通累加方法
public static void main(String[] args) {
// 定义时间
long startTime = System.currentTimeMillis();
// 定义一个数组,随机生成MAX_ARRAY个整型数
int[] array = MakeArray.makeArray();
// 定义一个sum变量进行累加统计
int sum = 0;
// 求和
for (int i = 0; i < array.length; i++) {
sum += array[i];
}
// 输出
System.out.println(
MakeArray.MAX_ARRAY + "个数,总和为sum = " + sum + "。共耗时:" + (System.currentTimeMillis() - startTime));
}
}
控制台输出结果:
1000000个数,总和为sum = -1369059355。共耗时:28
定义多线程求和类
public class SumArray {
private static class SumTask extends RecursiveTask<Integer> {
private static final long serialVersionUID = 1L;
// 定义一个阈值
private static final int THERHOLD = MakeArray.MAX_ARRAY / 10;
// 定义一个要操作的数组
private int[] sum;
// 定义起始下标
private int start;
// 定义结尾下标
private int end;
public SumTask(int[] sum, int start, int end) {
this.sum = sum;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start < THERHOLD) {
int count = 0;
for (int i = start; i < end; i++) {
count += sum[i];
}
return count;
} else {
int mid = (start + end) / 2;
SumTask sumTaskA = new SumTask(sum, start, mid);
SumTask sumTaskB = new SumTask(sum, mid + 1, end);
// 将线程阻塞,必须所有任务执行完成之后,统一返回
invokeAll(sumTaskA, sumTaskB);
return sumTaskA.join() + sumTaskB.join();
}
}
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
System.out.println("线程开始运行...");
ForkJoinPool pool = new ForkJoinPool();
int[] sum = MakeArray.makeArray();
SumTask sumTask = new SumTask(sum, 0, sum.length - 1);
pool.invoke(sumTask);
System.out.println(MakeArray.MAX_ARRAY + "个数据,总和 sum = " + sumTask.join() + ", 共耗时:"
+ (System.currentTimeMillis() - startTime));
}
}
控制台输出结果:
线程开始运行...
1000000个数据,总和 sum = -941995665, 共耗时:35
我们发现:
单线程求和共耗时:28ms
多线程求和共耗时:35ms
这样可以给我们一种警示,并非引入多线程就一定能提升效率,而是要看实际情况,经过多轮测试选择合适的方式来进行操作。
可以引申出其他的场景:
统计某个系统下的文件夹数量、超大规模的一些操作,例如几百万,上千万的数据库等操作等等场景。大家都可以根据不同的场景进行多多测试。个人感觉未来这种多线程递归操作会用的很多的地方,希望通过学习拓展思路
来自享学IT教育课后总结。