如何在 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 个条目)并且我需要使用多个线程来相当快地完成这项工作。
但是我需要每个线程都从一个唯一的索引开始,这样就不会有两个线程重复访问列表的同一部分。将有 100
的 batchSize
,因此每个线程将从其 startIndex
循环到 startIndex + 100
。
有什么办法可以实现吗?请注意,我这里只执行读操作,不执行写操作。列表中的每个条目只是一个字符串,它实际上是一个 SQL 查询,然后我通过 JDBC.
对数据库执行该查询
如果您只打算读取 List
,而不打算改变它,您可以简单地定义 Runnable
以将 List
和 startIndex
作为构造函数参数.只要没有线程同时修改它,并发读取 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 和 Queue
s。只需将所有工作件放入队列中,让执行者解决所有问题。
有一个原子变量:
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
}
我需要遍历一个大的 ArrayList
(约 50,000 个条目)并且我需要使用多个线程来相当快地完成这项工作。
但是我需要每个线程都从一个唯一的索引开始,这样就不会有两个线程重复访问列表的同一部分。将有 100
的 batchSize
,因此每个线程将从其 startIndex
循环到 startIndex + 100
。
有什么办法可以实现吗?请注意,我这里只执行读操作,不执行写操作。列表中的每个条目只是一个字符串,它实际上是一个 SQL 查询,然后我通过 JDBC.
对数据库执行该查询如果您只打算读取 List
,而不打算改变它,您可以简单地定义 Runnable
以将 List
和 startIndex
作为构造函数参数.只要没有线程同时修改它,并发读取 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 和 Queue
s。只需将所有工作件放入队列中,让执行者解决所有问题。
有一个原子变量:
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
}