如何在 Java 中使用多个线程迭代一个集合,其中没有两个线程迭代集合的同一部分?

How to use multiple threads in Java to iterate over a Collection where no two threads ever iterate over the same part of the Collection?

我需要遍历一个大的 ArrayList(约 50,000 个条目)并且我需要使用多个线程来相当快地完成这项工作。

但是我需要每个线程都从一个唯一的索引开始,这样就不会有两个线程重复访问列表的同一部分。将有 100batchSize,因此每个线程将从其 startIndex 循环到 startIndex + 100

有什么办法可以实现吗?请注意,我这里只执行读操作,不执行写操作。列表中的每个条目只是一个字符串,它实际上是一个 SQL 查询,然后我通过 JDBC.

对数据库执行该查询

如果您只打算读取 List,而不打算改变它,您可以简单地定义 Runnable 以将 ListstartIndex 作为构造函数参数.只要没有线程同时修改它,并发读取 ArrayList(即使是相同的索引)也没有危险。

为了安全起见,请务必将您的 ArrayList 包裹在对 Collections.unmodifiableList() 的调用中,并将 that List 传递给您的 Runnable秒。这样你就可以确信线程不会修改支持 ArrayList.

或者,您可以在主线程中构造子列表(使用 List.subList()),这样您就不需要将 startIndex 传递给每个线程。但是,您仍然希望在这样做之前使子列表不可修改。一个六个,另一个六个。

更好的方法是使用Guava's ImmutableList;它自然是线程安全的。

Java8中也有parallel streams,但要注意这个解决方案;它们很强大,但很容易出错。

如果用Java8,看list.stream().parallel()

对于 Java 7,在线程外使用 subList() 将工作分成几部分。然后线程应该只对这样的子列表进行操作。对于大多数列表,subList() 是一种非常有效的操作,它不会复制数据。如果支持列表被修改,那么你会得到一个 ConcurrentModificationException

作为将数据泵送到线程,我建议查看 Executor API 和 Queues。只需将所有工作件放入队列中,让执行者解决所有问题。

有一个原子变量:

int nextBatch = 0;

每次线程消耗新批次时增加它:

public synchronized int getNextBatch() {
    nextBatch += batchSize;
    if(nextBatch >= arraylist.size()) {
        // The end was reached
        return -1;
    }
    return nextBatch;
}

线程将调用此方法并获取我们需要处理的范围:

int start = getNextBatch();
if(start == -1) {
    // The end was reached
}
int end = Math.min(start + batchSize, arraylist.size);

// Iterate over its own range
for(int i = start; i < end; i++) {
    Object obj = arraylist.get(i);
    // Do something with obj
}