我有这段代码,我有自己的自制数组类,我想用它来测试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
是前三个任务是如此之小,以至于它们是由同一线程执行的。这也再次让你的总执行时间非常依赖于最后一个任务,在上的开销 executor.shutdown();
和executor.awaitTermination
。但是,即使是这种情况,任务的执行顺序以及随后将执行的线程通常也是不确定的,因此也不是你的应用程序所依赖的。有趣的是,当我更改你的代码以立即执行任务(即 executor.execute)时ArrayIndexOutOfBoundsException
,到处都是。