4

我想以多线程方式阅读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 也可以,但同样,这可能是偶然的。

如果您有一个通用集合(不一定是列表)并且需要以多线程方式提取其内容,您在实践中如何做?

4

4 回答 4

2

这取决于收藏。如果在读取过程中没有发生结构变化 - 您可以同时读取它,这很好。大多数集合不会仅更改读取或迭代的结构,因此可以,但请确保在这样做之前阅读您正在使用的集合的文档。

例如,HashSet javadocs

请注意,此实现不同步。如果多个线程同时访问一个哈希集,并且至少有一个线程修改了该集,则必须在外部进行同步。

这意味着只要没有写入,同时从两个线程读取就可以了。


一种方法是拆分数据,让每个线程读取collection.size()/ numberOfThreads元素​​。
线程#i 将读取collection.size()/numThreads * icollection.size()/numThreads * (i+1)

(请注意,需要特别注意确保不会遗漏最后一个元素,可以通过将最后一个线程 frpm 设置为 来完成collection.size()/numThreads * icollection.size()但它可能会使最后一个线程做更多的工作,并且会让你等待挣扎的线程)。

另一种选择是使用间隔的任务队列,每个线程将在队列不为空时读取元素,并在给定的间隔中读取元素。队列必须同步,因为它同时被多个线程修改。

于 2012-12-19T12:46:08.533 回答
2

一般来说,通过迭代收集内容的成本不足以进行多线程。就是获取内容后对列表进行的操作。所以你应该做的是:

  1. 使用单线程获取内容并划分工作负载。
  2. 启动几个线程/作业来进行处理,给他们一个(大)工作量。确保线程不使用原始列表。
  3. 使用单个线程来组合结果。

如果您需要共享集合,请使用线程安全集合。它们可以通过使用Collections .synchronized... 函数来创建。但是请记住,这意味着线程必须相互等待,如果您没有大量工作,那将使您的程序比单线程版本慢。

请注意,您在线程之间共享的所有对象都需要是线程安全的(例如,通过将所有访问包装在同步块中)。最好的信息来源是实践中的并发

于 2012-12-19T12:47:52.570 回答
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);
            }
        }
    };
}

这是线程安全的,每个线程都可以在初始集合的一部分上独立于其他线程工作。

于 2012-12-19T13:23:09.313 回答
1

您可以使用从 中获得的同步版本java.util.Collections。或者您可以尝试java.util.concurrent(例如ConcurrentHashMap)中的特殊数据结构。

我更喜欢其中任何一个而不是自己滚动。

另一个想法是在必要时同步整个方法,而不仅仅是集合访问。

请记住,不可变对象始终是线程安全的。您只需要同步共享的、可变的状态。

于 2012-12-19T12:45:48.820 回答