2012-12-19 55 views
4

我想以多线程的方式将读取为一个java集合的内容。在相同的背景下,这里有很多问题,但没有具体的读取点。以线程安全的方式获取集合的内容

我有一个整数的集合。我只是想要几个线程来遍历它,每个线程一次拉一个整数。我想确保所有集合都被迭代,并且没有整数被两个不同的线程拉两次。

坦率地说,我不知道什么是有效的。我知道迭代器不是线程安全的,但是当涉及到只读时,我不知道。我做了一些测试,以尝试并获得线故障,但没有达到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也可以,但同样可能是偶然的。

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

回答

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); 
      } 
     } 
    }; 
} 

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

+1

谢谢!我甚至不知道队列。改变了我的生活! –

1

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

我更喜欢那些滚动我自己的。

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

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

+0

好的。但是对于你的想法:如果我同步整个方法,我就失去了多线程的好处。 –

+1

@ Jean-Yves:不是。我相信'ConcurrentHashMap'使用细粒度的同步和/或[读写器锁定](http://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock)来允许同时进行多次读取。可能是错误的。但是,这种同步的开销仍然很大,如果您知道特定实例未更改集合的状态,则可以避免这种开销。 – amit

2

这取决于集合。如果在阅读过程中没有发生结构变化 - 你可以同时阅读,没关系。大多数集合不会仅仅为了读取或迭代而改变结构,所以它是可以的,但是确保在读取之前阅读正在使用的集合的文档。

例如,HashSet javadocs

注意,此实现不是同步的。如果多个线程 同时访问散列集,并且至少一个线程修改了该集,则它必须在外部同步。

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


一种方式做到这一点是分割数据,并让每个线程读取collection.size()/ numberOfThreads元素。
线程I路将宣读collection.size()/numThreads * icollection.size()/numThreads * (i+1)

(注意特殊照顾,将需要保证最后一个元素没有错过,它可以通过设置最后一个线程FRPM collection.size()/numThreads * icollection.size()完成,但它可能使最后一个线程做了更多的工作,并且会让你等待挣扎的线程)。

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

+0

好的谢谢。所以,如果我回顾一下你指的是什么,这就意味着我的不安全的例子没有一个普遍的事实,并且最终取决于Collection的具体实现。 –

+0

@ Jean-Yves:最终,是的。我不知道任何收藏在阅读过程中实际上会改变结构 - 但这并不意味着它没有,最终取决于具体的实例。 – amit

2

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

  1. 使用单线程来获取内容和分工作量。
  2. 启动多个线程/作业来执行处理,为他们提供(大)工作量。确保线程不使用原始列表。
  3. 使用单个线程来合并结果。

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

请注意,您在线程之间共享的所有对象都必须是线程安全的(例如,通过封装同步块中的所有访问)。关于这方面的最佳信息来源是Concurrency in Practise

相关问题