2009-09-08 81 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