2012-12-19 59 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

相關問題