2009-09-08 46 views
38

我需要一個數據結構,它是一個LinkedHashMap並且是線程安全的。java是否有「LinkedConcurrentHashMap」數據結構?

我該怎麼做?

+6

我認爲這個問題沒有詳細說明。你爲什麼說它需要是「一個LinkedHashMap」?你真的在做什麼行爲? – 2009-11-04 18:12:55

+0

類似的問題:http://stackoverflow.com/questions/1815646/how-to-implement-concurrenthashmap-with-features-similar-in-linkedhashmap – Vadzim 2015-01-22 12:08:47

回答

4

Collections.synchronizedMap(new LinkedHashMap())

+2

但是如果你想做任何複合,你仍然需要手動同步像ConcurrentHashMap – 2009-09-08 21:47:25

24

你可以用地圖在Collections.synchronizedMap得到同步的HashMap維護插入順序。這並不像ConcurrentHashMap那樣高效(並且沒有實現ConcurrentMap的額外接口方法),但它確實爲您提供了(稍微)線程安全的行爲。

即使是強大的谷歌集合似乎沒有尚未已經解決了這個特殊的問題。然而,有one project,試圖解決這個問題。

我有點說的同步,因爲迭代仍然沒有線程在這個意義上,併發修改例外可能發生的安全。

+1

+1提供的額外操作。我只想補充一點,就像我記得的那樣,它不存在的原因是因爲爲了保留訪問順序的行爲,它或多或少會被強制鎖定整個表(或者必須過於複雜一種可能會導致死鎖或緩慢的方式),所以圍繞一個普通的LinkedHashMap進行同步在大多數情況下會或多或少地具有相同的效率。解釋可能是粉飾,但對我來說很有意義。 – Fredrik 2010-01-16 22:13:28

+1

(免責聲明:我沒有投票也沒有投票)。不喜歡這種方法。同步!=併發。如果你在地圖上有一個密集的並行作業,你的n-1線程將一直被阻塞。儘管併發應避免在大多數情況下阻塞。 – juanmf 2017-02-15 02:08:26

3

由於ConcurrentHashMap提供了一些不在Map接口中的重要的額外方法,簡單地用LinkedMap包裝LinkedHashMap不會給你相同的功能,特別是它們不會給你任何類似putIfAbsent (),替換(key,oldValue,newValue)和remove(key,oldValue)方法,使ConcurrentHashMap如此有用。

除非有已經實現你想要的一些阿帕奇庫,你可能不得不使用LinkedHashMap中,並提供適當的同步{}自己的塊。

+0

嗯,六年後downvote,沒有理由。這不是因爲問題改變了,所以我的答案現在回答了錯誤的問題。想法? – 2015-12-08 23:55:56

0

答案是幾乎沒有,沒有什麼相當於排序(如LinkedHashMap的)一個ConcurrentHashMap的。正如其他人指出的那樣,你可以使用Collections.synchronizedMap(-yourmap-)來包裝你的集合,但是這不會給你相同級別的細粒度鎖定。它會在每個操作中阻止整個地圖。

最好的辦法是在任何訪問地圖的時候使用同步(當然重要),或者在地圖上編寫一個封裝程序來確定應該何時訪問它或者不應該鎖定。

+1

如果讀取到hashmap的非同步讀取,讀取可能不僅僅是髒的,它們可能會損壞,拋出異常等,尤其是在多CPU環境中。 – Yishai 2009-09-08 05:00:04

+2

由於併發更新的干擾,存在HashMap進入無限循環的情況。 – 2009-09-08 21:49:21

10

這個問題有很多不同的方法。你可以使用:

Collections.synchronizedMap(new LinkedHashMap()); 

爲其他答覆建議,但這有幾個陷阱,你需要做到心中有數。最值得注意的是,當迭代集合時,您通常需要保持集合同步鎖定,這反過來會阻止其他線程訪問集合,直到您完成迭代。 (See Java theory and practice: Concurrent collections classes)。例如:

synchronized(map) { 
    for (Object obj: map) { 
     // Do work here 
    } 
} 

使用

new ConcurrentHashMap(); 

可能是一個更好的選擇,因爲你將不再需要鎖定集合遍歷它。

最後,你可能要考慮一個更功能編程方法。那就是你可以認爲地圖本質上是不可變的。您可以創建一個包含舊地圖內容和新添加內容的新地圖,而不是添加到現有地圖。這聽起來很奇怪,但它實際上是這樣的Scala deals with concurrency and collections

+0

感謝您指出斯卡拉如何解決這個問題。這真是讓人大開眼界。 – 2013-07-12 02:15:49

+0

我猜java也在CopyOnWriteArrayList中使用這種方法,它在每次寫入後複製列表,這聽起來性能廣泛,但這就是我猜測的結果。 – 2016-12-19 07:38:09

7

有谷歌代碼下的one implementation。來自他們網站的報價:

用作軟件緩存的高性能版本的java.util.LinkedHashMap。

設計

  • 一個並行鏈表貫穿即ConcurrentHashMap提供驅逐排序。
  • 支持插入和訪問有序驅逐策略(FIFO,LRU和Second Chance)。
+2

這個項目的主要提交者是Ben Manes,一位谷歌軟件工程師,所以它非常值得信賴(恕我直言)。 – Marco 2013-12-12 22:50:54

4

可以使用ConcurrentSkipListMap,只在Java SE/EE 6或更高版本可用。這是訂單presevering因爲按鍵排序根據其自然順序。您需要有一個比較器或創建關鍵字Comparable對象。爲了模仿鏈接的哈希映射行爲(迭代順序是添加條目的時間順序),我實現了我的關鍵對象,以便始終比較大於給定的其他對象,除非它是相等的(無論對於您的對象)。 因爲如 http://www.ibm.com/developerworks/java/library/j-jtp07233.html中所述,所包含的同步鏈接哈希映射並不足夠:「同步集合包裝器,synchronizedMap和synchronizedList有時被稱爲有條件線程安全 - 所有單個操作都是線程安全的,但操作序列控制流取決於先前操作的結果可能會受到數據競爭的影響清單1中的第一個片段顯示了普通的put-if-absent方法 - 如果一個條目尚不存在於Map中,則添加它。在另一個線程中,在containsKey()方法返回的時間和put()方法被調用的時間之間插入一個具有相同鍵值的值是可能的。如果你想確保一次插入,你需要包裝一對帶有同步塊的語句在Map m上同步。「

所以只有一個ConcurrentSkipListMap比正常的ConcurrentHashMap慢3-5倍。

+2

不幸的是,定義插入順序的自定義比較器不起作用。這假定傳遞給compare()的第一個對象是新插入的對象。但ConcurrentSkipList映射在迭代時也會調用比較(至少使用'map.entrySet()'),在這種情況下,您不知道兩個對象中的哪一個是第一個插入的。除非這個對象包含一個創建日期 - 但是這對於Strings常見的例子來說是失敗的。 – 2014-04-07 22:21:10

1

我剛剛嘗試基於插入順序的同步有界LRU映射LinkedConcurrentHashMap;用讀/寫鎖進行同步。 所以當你使用迭代器;您必須獲取WriteLock以避免ConcurrentModificationException。
這比Collections.synchronizedMap.更好

public class LinkedConcurrentHashMap<K, V> { 

    private LinkedHashMap<K, V> linkedHashMap = null; 
    private final int cacheSize; 
    private ReadWriteLock readWriteLock = null; 

    public LinkedConcurrentHashMap(LinkedHashMap<K, V> psCacheMap, int size) { 
     this.linkedHashMap = psCacheMap; 
     cacheSize = size; 
     readWriteLock=new ReentrantReadWriteLock(); 
    } 

    public void put(K key, V value) throws SQLException{ 
     Lock writeLock=readWriteLock.writeLock(); 
     try{ 
      writeLock.lock(); 
      if(linkedHashMap.size() >= cacheSize && cacheSize > 0){ 
       K oldAgedKey = linkedHashMap.keySet().iterator().next(); 
       remove(oldAgedKey); 
      } 
      linkedHashMap.put(key, value); 
     }finally{ 
      writeLock.unlock(); 
     } 
    } 

    public V get(K key){ 
     Lock readLock=readWriteLock.readLock(); 
     try{ 
      readLock.lock(); 
      return linkedHashMap.get(key); 
     }finally{ 
      readLock.unlock(); 
     } 
    } 

    public boolean containsKey(K key){ 
     Lock readLock=readWriteLock.readLock(); 
     try{ 
      readLock.lock(); 
      return linkedHashMap.containsKey(key); 
     }finally{ 
      readLock.unlock(); 
     } 
    } 

    public V remove(K key){ 
     Lock writeLock=readWriteLock.writeLock(); 
     try{ 
      writeLock.lock(); 
      return linkedHashMap.remove(key); 
     }finally{ 
      writeLock.unlock(); 
     } 
    } 

    public ReadWriteLock getLock(){ 
     return readWriteLock; 
    } 

    public Set<Map.Entry<K, V>> entrySet(){ 
     return linkedHashMap.entrySet(); 
    } 
} 
+0

如果你實現'ConcurrentMap ',你可以免費獲得同步(不需要'ReadWriteLock')。 – naXa 2015-03-04 07:31:30

+0

@naXa我剛試過LRU地圖,所以在地圖滿了的時候,添加一個新元素也應該刪除最近使用過的元素,所以我需要鎖定實現。 – 2015-03-04 08:21:14

+0

我知道LRUMap可以通過LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder)實現,使用accessOrder布爾值爲true。但是,這是不同步的。 – 2015-03-04 08:23:35