我需要一个LinkedHashMap的数据结构,并且是线程安全的。
我该怎么做?
答案 0 :(得分:29)
您可以将地图包装在Collections.synchronizedMap中以获取维护插入顺序的同步散列映射。这不如ConcurrentHashMap有效(并且不实现ConcurrentMap的额外接口方法),但它确实可以获得(某种程度上)线程安全的行为。
即使是强大的Google Collections似乎也没有解决这个特定问题。但是,one project确实试图解决这个问题。
我在某种程度上说同步,因为迭代在并发修改异常可能发生的意义上仍然不是线程安全的。
答案 1 :(得分:14)
这个问题有很多不同的方法。你可以使用:
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
的方式答案 2 :(得分:8)
Google代码下有one implementation个版本。来自他们网站的引用:
java.util.LinkedHashMap的高性能版本,用作软件缓存。
<强>设计强>
- 并发链表通过ConcurrentHashMap运行,以提供驱逐顺序。
- 支持插入和访问有序驱逐策略(FIFO,LRU和第二次机会)。
答案 3 :(得分:5)
您可以使用ConcurrentSkipListMap,仅在Java SE / EE 6或更高版本中可用。它是预先订购的,因为按键按照它们的自然顺序排序。您需要有一个比较器或使键可比对象。为了模仿链接的哈希映射行为(迭代顺序是添加条目的时间顺序),我实现了我的密钥对象,总是比较大于给定的其他对象,除非它是相等的(无论你的对象是什么) )。 包装的同步链接哈希映射是不够的,因为如中所述 http://www.ibm.com/developerworks/java/library/j-jtp07233.html:“同步集合包装器,synchronizedMap和synchronizedList有时称为条件线程安全 - 所有单独的操作都是线程安全的,但控制流取决于先前操作结果的操作序列可能是清单1中的第一个片段显示了常见的put-if-absent习惯用法 - 如果Map中还没有条目,则添加它。不幸的是,如同编写的那样,另一个线程可能会插入一个在containsKey()方法返回的时间与调用put()方法的时间之间使用相同键的值。如果要确保一次插入一次,则需要使用同步块来包装这对语句。地图m。“
唯一有用的是ConcurrentSkipListMap,它比普通的ConcurrentHashMap慢3-5倍。
答案 4 :(得分:4)
Collections.synchronizedMap(new LinkedHashMap())
答案 5 :(得分:3)
由于ConcurrentHashMap提供了一些不在Map接口中的重要额外方法,简单地用LinkedMap包装LinkedHashMap不会给你相同的功能,特别是它们不会给你任何类似putIfAbsent的东西( ),替换(key,oldValue,newValue)和remove(key,oldValue)方法,这些方法使ConcurrentHashMap非常有用。
除非有一些已经实现了你想要的apache库,否则你可能不得不使用LinkedHashMap并提供你自己的合适的{}块。
答案 6 :(得分:1)
我刚刚根据插入顺序 LinkedConcurrentHashMap 尝试了同步有界LRU Map;使用读/写锁定进行同步。
所以当你使用迭代器时;你必须获得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();
}
}
答案 7 :(得分:0)
答案几乎没有,没有什么能比得上排序的ConcurrentHashMap(比如LinkedHashMap)。正如其他人所指出的,你可以使用Collections.synchronizedMap(-yourmap-)来包装你的集合,但是这不会给你相同级别的细粒度锁定。它只会在每次操作时阻止整个地图。
你最好的选择是在对地图的任何访问中使用synchronized(当然重要的地方。例如,你可能不关心脏读)或者在地图周围写一个包装器来决定它应该什么时候或者不应该锁定。