java—向executorservice添加更多线程只会使其速度变慢

kuuvgm7e  于 2021-07-06  发布在  Java
关注(0)|答案(1)|浏览(487)

我有这段代码,其中我有自己自制的array类,我想用它来测试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的drivercode在arraylist上执行一系列操作,并有4个不同的任务执行,每个任务执行10万次。
问题是当我给线程池更多的线程“executors.newfixedthreadpool(2);”它只会变得更慢。例如,对于一个线程,典型的计时时间是1.0366974毫秒,但是如果我用3个线程运行它,时间会上升到5.7932714毫秒。
怎么回事?为什么更多的线程会慢得多?
编辑:
为了简化这个问题,我做了一个更简单的drivercode,它有四个任务可以简单地添加元素:

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");

在这里,分配多少线程似乎仍然无关紧要,根本没有加速,这可能仅仅是因为开销吗?

hrirmatl

hrirmatl1#

代码中存在一些问题,很难解释为什么线程越多时间就越长。
顺便说一句

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` 到处都是。

相关问题