我想以多线程方式读取 java Collection的内容。这里有很多关于相同上下文的问题,但没有关于具体的读取点。
我有一个整数集合。我只想要几个线程迭代它,每个线程一次拉一个整数。我想确保迭代所有集合,并且没有整数被两个不同的线程拉两次。
坦率地说,我不知道什么有效。我知道迭代器不是线程安全的,但是当涉及到只读时我不知道。我做了一些测试试图获得线程故障,但没有达到100%的确定性:int imax = 500;
Collection<Integer> li = new ArrayList<Integer>(imax);
for (int i = 0; i < imax; i++) {
li.add(i);
}
final Iterator<Integer> it = li.iterator();
Thread[] threads = new Thread[20];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread("Thread " + i) {
@Override
public void run() {
while(it.hasNext()) {
System.out.println(it.next());
}
}
};
}
for (int ithread = 0; ithread < threads.length; ++ithread) {
threads[ithread].setPriority(Thread.NORM_PRIORITY);
threads[ithread].start();
}
try {
for (int ithread = 0; ithread < threads.length; ++ithread)
threads[ithread].join();
} catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
编辑: 在实际用例中,每个整数用于开始密集工作,例如查找是否为素数。
以上示例提取整数列表,没有重复或遗漏,但我不知道它是否是偶然的。
使用HashSet而不是ArrayList也可以,但同样可能是偶然的。
如果你有一般的集合(不一定是列表)并且需要以多线程方式提取其内容,你在实践中如何做?
答案 0 :(得分:2)
这取决于收藏品。如果在读取过程中没有发生结构变化 - 您可以同时读取它,这很好。大多数集合不会仅仅更改读取或迭代的结构,所以没关系,但在执行此操作之前,请务必阅读您正在使用的集合的文档。
例如,HashSet javadocs:
请注意,此实现未同步。如果有多个线程 同时访问哈希集,以及至少一个线程 修改集合,必须在外部同步。
这意味着只要没有写,就可以同时从两个线程中读取就好了。
一种方法是拆分数据,让每个线程读取collection.size()/ numberOfThreads
个元素。
线程#i将从collection.size()/numThreads * i
读取到collection.size()/numThreads * (i+1)
(注意需要特别注意保证最后的元素不会丢失,可以通过将最后一个线程frpm collection.size()/numThreads * i
设置为collection.size()
来完成,但它可能会使最后一个线程做得多更多的工作,并会让你等待挣扎的线程。)
另一种选择是使用间隔的任务队列,每个线程将在队列不为空时读取元素,并以给定的间隔读取元素。队列必须同步,因为它会被多个线程同时修改。
答案 1 :(得分:2)
通常,迭代收集内容的成本不足以进行多线程处理。这是您在获取内容后对列表执行的操作。 所以你应该做的是:
如果您需要共享集合,请使用线程安全集合。可以使用Collections。synchronized ......函数创建它们。但是请记住,这意味着线程必须等待彼此,如果你没有相当大的工作,这将使你的程序比单线程版本慢。
请注意,您在线程之间共享的所有对象都需要是线程安全的(例如,通过将所有访问包装在synchronized块中)。关于此的最佳信息来源是Concurrency in Practise
答案 2 :(得分:2)
您的用例将受益于使用队列 - 有一些线程安全的实现,例如ArrayBlockingQueue。
Collection<Integer> li = new ArrayList<Integer>(imax);
final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(li.size(), false, li);
Thread[] threads = new Thread[20];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread("Thread " + i) {
@Override
public void run() {
Integer i;
while ((i = queue.poll()) != null) {
System.out.println(i);
}
}
};
}
这是线程安全的,并且每个线程可以在初始集合的一部分上独立于其他线程工作。
答案 3 :(得分:1)
您可以使用java.util.Collections
中提供的同步版本。或者您可以在java.util.concurrent
中尝试特殊数据结构(例如ConcurrentHashMap
)。
我更喜欢其中任何一个自己动手。
另一个想法是在必要时同步整个方法,而不仅仅是集合访问。
请记住,不可变对象始终是线程安全的。您只需要同步共享的可变状态。