java - 向 executorservice 添加更多线程只会使其变慢
问题描述
我有这段代码,我有自己的自制数组类,我想用它来测试java中一些不同并发工具的速度
public class LongArrayListUnsafe {
private static final ExecutorService executor
= Executors.newFixedThreadPool(1);
public static void main(String[] args) {
LongArrayList dal1 = new LongArrayList();
int n = 100_000_000;
Timer t = new Timer();
List<Callable<Void>> tasks = new ArrayList<>();
tasks.add(() -> {
for (int i = 0; i <= n; i+=2){
dal1.add(i);
}
return null;
});
tasks.add(() -> {
for (int i = 0; i < n; i++){
dal1.set(i, i + 1);
}
return null;});
tasks.add(() -> {
for (int i = 0; i < n; i++) {
dal1.get(i);
}
return null;});
tasks.add(() -> {
for (int i = n; i < n * 2; i++) {
dal1.add(i + 1);
}
return null;});
try {
executor.invokeAll(tasks);
} catch (InterruptedException exn) {
System.out.println("Interrupted: " + exn);
}
executor.shutdown();
try {
executor.awaitTermination(1000, TimeUnit.MILLISECONDS);
} catch (Exception e){
System.out.println("what?");
}
System.out.println("Using toString(): " + t.check() + " ms");
}
}
class LongArrayList {
// Invariant: 0 <= size <= items.length
private long[] items;
private int size;
public LongArrayList() {
reset();
}
public static LongArrayList withElements(long... initialValues){
LongArrayList list = new LongArrayList();
for (long l : initialValues) list.add( l );
return list;
}
public void reset(){
items = new long[2];
size = 0;
}
// Number of items in the double list
public int size() {
return size;
}
// Return item number i
public long get(int i) {
if (0 <= i && i < size)
return items[i];
else
throw new IndexOutOfBoundsException(String.valueOf(i));
}
// Replace item number i, if any, with x
public long set(int i, long x) {
if (0 <= i && i < size) {
long old = items[i];
items[i] = x;
return old;
} else
throw new IndexOutOfBoundsException(String.valueOf(i));
}
// Add item x to end of list
public LongArrayList add(long x) {
if (size == items.length) {
long[] newItems = new long[items.length * 2];
for (int i=0; i<items.length; i++)
newItems[i] = items[i];
items = newItems;
}
items[size] = x;
size++;
return this;
}
public String toString() {
return Arrays.stream(items, 0,size)
.mapToObj( Long::toString )
.collect(Collectors.joining(", ", "[", "]"));
}
}
public class Timer {
private long start, spent = 0;
public Timer() { play(); }
public double check() { return (System.nanoTime()-start+spent)/1e9; }
public void pause() { spent += System.nanoTime()-start; }
public void play() { start = System.nanoTime(); }
}
LongArrayList 类的实现不是那么重要,它不是线程安全的。
带有 executorservice 的驱动程序代码在 arraylist 上执行了一堆操作,并且有 4 个不同的任务执行它,每个任务执行 100_000_000 次。
问题是当我给线程池更多线程时“Executors.newFixedThreadPool(2);” 它只会变得更慢。例如,对于一个线程,典型的时间是 1.0366974 毫秒,但如果我用 3 个线程运行它,时间会上升到 5.7932714 毫秒。
到底是怎么回事?为什么更多的线程这么慢?
编辑:
为了简化这个问题,我制作了这个更简单的驱动程序代码,它有四个任务,只需添加元素:
ExecutorService executor
= Executors.newFixedThreadPool(2);
LongArrayList dal1 = new LongArrayList();
int n = 100_000_00;
Timer t = new Timer();
for (int i = 0; i < 4 ; i++){
executor.execute(new Runnable() {
@Override
public void run() {
for (int j = 0; j < n ; j++)
dal1.add(j);
}
});
}
executor.shutdown();
try {
executor.awaitTermination(1000, TimeUnit.MILLISECONDS);
} catch (Exception e){
System.out.println("what?");
}
System.out.println("Using toString(): " + t.check() + " ms");
在这里,我分配多少线程似乎仍然无关紧要,根本没有加速,这可能仅仅是因为开销吗?
解决方案
您的代码存在一些问题,很难解释为什么使用更多线程会增加时间。
顺便提一句
public double check() { return (System.nanoTime()-start+spent)/1e9; }
给你秒而不是毫秒,所以改变这个:
System.out.println("Using toString(): " + t.check() + " ms");
至
System.out.println("Using toString(): " + t.check() + "s");
第一个问题:
LongArrayList dal1 = new LongArrayList();
dal1
在所有线程之间共享,并且这些线程正在更新该共享变量而没有任何mutual exclusion
围绕它,因此,导致竞争条件。此外,这也可能导致cache invalidation
,这可能会增加您的整体执行时间。
另一件事是您可能有负载平衡问题。您有 4 个并行任务,但显然是最后一个
tasks.add(() -> {
for (int i = n; i < n * 2; i++) {
dal1.add(i + 1);
}
return null;});
是计算最密集的任务。即使 4 个任务并行运行,没有我提到的问题(即共享数据缺乏同步),最后一个任务将决定整体执行时间。
更不用说并行性不是免费的,它会增加开销(例如, 调度并行工作等),这可能足够高,以至于一开始就不值得并行化代码。在您的代码中,至少存在等待任务完成的开销,以及关闭执行器池的开销。
另一种也可以解释为什么你没有得到ArrayIndexOutOfBoundsException
所有地方的可能性是前 3 个任务太小以至于它们由同一个线程执行。这也会再次使您的整体执行时间非常依赖于最后一个任务,即 和 的 executor.shutdown();
开销executor.awaitTermination
。但是,即使是这种情况,任务的执行顺序以及随后将执行哪些线程通常也是不确定的,因此,您的应用程序不应该依赖它。有趣的是,当我更改您的代码以立即执行任务(即 executor.execute)时,我得到了ArrayIndexOutOfBoundsException
所有的地方。
推荐阅读
- rabbitmq - 什么时候从队列中删除消息?
- html - 将 LocalStorage 数据动态解析成一张表
- python - pywinAuto - 使用 Windows MFC 控件
- actions-on-google - Google 助理可以访问网络帖子上的音频吗
- php - PHP SQL Server连接工作或不工作
- reactjs - 改进对 React 的错误处理 | 还原 | MERN 应用程序
- html - XSLT:如何避免在 HTML 的属性中添加自闭合标签
- c - 从函数返回多个错误之一并在 C 中结束程序的最佳实践是什么?
- ios - 等到应用内购买完成,直到执行操作
- css - 从底部绝对定位不创建溢出滚动条