首页 > 解决方案 > 向 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");

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

标签: javamultithreadingperformanceconcurrencyparallel-processing

解决方案


您的代码存在一些问题,很难解释为什么使用更多线程会增加时间。

顺便提一句

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所有的地方。


推荐阅读