线程对效率的影响
一个小实验,用来测试线程对性能的提升
- 计算数组中每一个数字乘以100的和。
- 使用双重循环计算,不用数学公式,这样计算时间长一点,容易做对比。
- 总共实现了四种不同的方式
- 使用单线程
- 使用4个手动写的线程
- 使用分治,先拷贝数组分成t份,之后进行合并
- 使用for循环生成4个手写的线程。
最后可以看到手动实现4个线程进行分治可以把效率提升到4倍左右。
详细代码请看下面代码1。
分析1:
- 由于我的计算机是8核16线程的,所以最多可以实现16个线程的并行计算。所以4个线程最多可以把效率提升到4倍。但是最多的效率提升就到16倍了。
- 使用分治,由于由大量的数组拷贝,所以计算的效率会低很多。
- 使用for循环创建线程,由于task的get()是阻塞的,会导致for循环没法执行,从而使得下面的线程没法执行。解决办法是
- 保存生成的task, 在for循环外面调用get方法。
- 之后的效率如下。
详细代码请看下面代码2。
分析2:提升线程个数带来的影响
- 可以看到最终的效率提升了15倍左右。
- 这是由于16个逻辑处理器并行工作的原因。
- 这么一看电脑设计的还不错。16个逻辑处理器能并行提升15倍的性能。这还是在上下文切换的情况下。在我代码垃圾的情况下。hhhh
详细代码请看下面代码3。
代码1
package threadBase.baseKey;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Random;
import java.util.Stack;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicInteger;
/*
*
*
* 线程启动的三种方式
* 1. implement
* 2. extends
* 3. FutureT
*
* 4.
* */
public class StartThread {
private static final int MAXN = 100000000;
private static int[] nums = new int[MAXN]; // 计算数组中100个数字的和
private AtomicInteger cur = new AtomicInteger();
static {
Arrays.fill(nums, 1);
}
private static long testSingle() throws Exception {
long startTime = System.currentTimeMillis();
long sum = 0;
for (int i = 0; i < MAXN; i++) {
for (int j = 0; j < 100; j++) {
sum += nums[i];
}
}
long endTime = System.currentTimeMillis();
System.out.println("单线程: ");
System.out.println("sum = " + sum + " t = " + (endTime - startTime) + "ms");
return endTime - startTime;
}
private static long test1() throws Exception{
long startTime = System.currentTimeMillis();
FutureTask<Long> task1 = new FutureTask<Long>(() -> {
long tsum = 0;
for (int i = 0; i < 25000000; i++) {
for (int j = 0; j < 100; j++) {
tsum += nums[i];
}
}
return tsum;
});
FutureTask<Long> task2 = new FutureTask<Long>(() -> {
long tsum = 0;
for (int i = 25000000; i < 50000000; i++) {
for (int j = 0; j < 100; j++) {
tsum += nums[i];
}
}
return tsum;
});
FutureTask<Long> task3 = new FutureTask<Long>(() -> {
long tsum = 0;
for (int i = 50000000; i < 75000000; i++) {
for (int j = 0; j < 100; j++) {
tsum += nums[i];
}
}
return tsum;
});
FutureTask<Long> task4 = new FutureTask<Long>(() -> {
long tsum = 0;
for (int i = 75000000; i < 100000000; i++) {
for (int j = 0; j < 100; j++) {
tsum += nums[i];
}
}
return tsum;
});
new Thread(task1).start();
new Thread(task2).start();
new Thread(task3).start();
new Thread(task4).start();
long sum = task1.get() + task2.get() + task3.get() + task4.get();
long endTime = System.currentTimeMillis();
System.out.println("4线程:");
System.out.println("sum = " + sum + " t = " + (endTime - startTime) + "ms");
return endTime - startTime;
}
private static long test2() throws Exception{
/*
*
* 首先需要一个线程进行任务的划分。
* 然后由这个划分线程生成划分任务的线程数目。
* 在有这个线程进程组装。
* 在这使用主线程进行划分。
* */
int t = 5; // 划分线程的数量
int len = MAXN / t;
long sum = 0;
long startTime = System.currentTimeMillis();
for (int i = 0; i < t; i++) { // 进行任务划分
int[] numt = new int[len];
for (int j = i * len; j < (i + 1) * len; j++) {
numt[j - (i * len)] = nums[j];
}
// 线程执行
FutureTask<Long>task = new FutureTask<Long>(()->{
long ans = 0;
for (int x: numt) {
for (int j = 0; j < 100; j++) {
ans += x;
}
}
return ans;
});
new Thread(task).start();
sum += task.get();
}
long endTime = System.currentTimeMillis();
System.out.println("使用分治进行" + t + "线程划分执行:");
System.out.println("sum = " + sum + " t = " + (endTime - startTime) + "ms");
return endTime - startTime;
}
private static long test3() throws Exception {
StartThread startThread = new StartThread();
int cnt = 4; // 控制线程的个数
int sz = MAXN / cnt; // 每一个线程计算的数量是多少
long sum = 0; // 计算和是多少
long startTime = System.currentTimeMillis();
for (int i = 0; i < cnt; i++) {
FutureTask<Long> task = new FutureTask<Long>(()->
{
long ans = 0L;
int bg = startThread.cur.getAndIncrement();
for (int j = bg * sz; j < (bg + 1) * sz; j++) {
for (int k = 0; k <100; k++) {
ans += nums[j];
}
}
return ans;
});
new Thread(task).start();
sum += task.get();
}
long endTime = System.currentTimeMillis();
System.out.println(cnt + "个线程:");
System.out.println("sum = " + sum + " t = " + (endTime - startTime) + "ms");
return endTime - startTime;
}
// 可以从第三遍开始,统计一个平均的时间
public static void main(String[] args)throws Exception {
long t1 = 0, t2 = 0, t3 = 0, t4 = 0;
for (int i = 0; i < 11; i++) { // 后面的8轮次进行统计
System.out.println("-----------第" + i + "轮----------");
if (i >= 3) {
t1 += testSingle();
t2 += test1();
t3 += test2();
t4 += test3();
} else {
testSingle();
test1();
test2();
test3();
}
}
System.out.println("平均时间:");
System.out.println("单线程:" + t1 / 8 + "ms");
System.out.println("4个手动多线程:" + t2 / 8 + "ms");
System.out.println("4个分治多线程:" + t3 / 8 + "ms");
System.out.println("for循环多线程:" + t4 / 8 + "ms");
}
}
代码2
主要就是修改了test2和test3的方法。
- 把task.get()放在循环外面计算。
- 使用数组保存生成的task。
private static long test2() throws Exception{
/*
*
* 首先需要一个线程进行任务的划分。
* 然后由这个划分线程生成划分任务的线程数目。
* 在有这个线程进程组装。
* 在这使用主线程进行划分。
* */
int t = 5; // 划分线程的数量
int len = MAXN / t;
long sum = 0;
long startTime = System.currentTimeMillis();
FutureTask<Long>[]tasks = new FutureTask[t];
for (int i = 0; i < t; i++) { // 进行任务划分
int[] numt = new int[len];
for (int j = i * len; j < (i + 1) * len; j++) {
numt[j - (i * len)] = nums[j];
}
// 线程执行
FutureTask<Long>task = new FutureTask<Long>(()->{
long ans = 0;
for (int x: numt) {
for (int j = 0; j < 100; j++) {
ans += x;
}
}
return ans;
});
new Thread(task).start();
tasks[i] = task;
// sum += task.get(); // 这个会阻塞线程,所以会慢
}
for (int i = 0; i < 4; i++) {
sum += tasks[i].get();
}
long endTime = System.currentTimeMillis();
System.out.println("使用分治进行" + t + "线程划分执行:");
System.out.println("sum = " + sum + " t = " + (endTime - startTime) + "ms");
return endTime - startTime;
}
public static long test4() throws Exception{
StartThread startThread = new StartThread();
int cnt = 4; // 控制线程的个数
int sz = MAXN / cnt; // 每一个线程计算的数量是多少
long sum = 0; // 计算和是多少
long startTime = System.currentTimeMillis();
FutureTask<Long>[]tasks = new FutureTask[cnt];
for (int i = 0; i < cnt; i++) {
FutureTask<Long> task = new FutureTask<Long>(()->
{
long ans = 0L;
int bg = startThread.cur.getAndIncrement();
for (int j = bg * sz; j < (bg + 1) * sz; j++) {
for (int k = 0; k <100; k++) {
ans += nums[j];
}
}
return ans;
});
new Thread(task).start();
tasks[i] = task;
}
for (int i = 0; i < cnt; i++) {
sum += tasks[i].get();
}
long endTime = System.currentTimeMillis();
System.out.println(cnt + "个线程:");
System.out.println("sum = " + sum + " t = " + (endTime - startTime) + "ms");
return endTime - startTime;
}
代码3
- 测试多少个线程对性能提升最大
- 结论是逻辑处理器的个数个。
public static void testNumOfThread() throws Exception{
int cnt = 32;
long []times = new long[cnt];
for (int i = 1; i < cnt; i++) {
times[i] = test4(i);
}
for (int i = 1; i < cnt; i++) {
System.out.println(i + "个线程:" + times[i] + "ms");
}
}
// 可以从第三遍开始,统计一个平均的时间
public static void main(String[] args)throws Exception {
testNumOfThread();
}
总结
- task.get()是阻塞的,最好不要放在主线程中,更不要放在线程创建的路径上,最好在开一个线程,进行归并。
- 多线程对效率的提升体现在多处理器的并行上。
- 这里实现的计算是平均划分数组进行求和,如果不能平均划分就会出错。应该使用归并式的那种划分。
- 明天实现一下多线程的归并排序和多线程的斐波那契数列。