我有一个特定的用例,但无法确定要使用的正确数据结构。
我有一个线程可以将对象保存到HashMap中。类似于市场数据的东西,你有一个很高且未知的滴答频率。
另一个线程不断按特定顺序按键读取此映射以获取更新的Price对象和查询。对于给定周期中的相同密钥,查询可以是多次。读取和写入非常频繁,但读取线程只对最新可用数据感兴趣,这些数据已完全更新,并且在写入完成之前不一定会阻塞。
我希望您对这种用例的理想数据结构有所了解。是否有比ConcurrentHashMap更好的实现?
由于
答案 0 :(得分:2)
ConcurrentHashMap。来自Javadoc
支持检索的完全并发和可调整的哈希表 预期的更新并发性。本课程遵循相同的功能 规范为Hashtable,包括方法版本 对应于Hashtable的每种方法。但是,尽管如此 操作是线程安全的,检索操作不需要 锁定,并没有任何支持锁定整个表 一种阻止所有访问的方法。这个类可以完全互操作 Hashtable在程序中依赖于其线程安全但不依赖于它 同步细节。
检索操作(包括get)一般不会阻塞,所以可能 与更新操作重叠(包括put和remove)。检索 反映最近完成的更新操作的结果 坚持他们的发作。对于诸如putAll和。之类的聚合操作 清除,并发检索可能仅反映插入或删除 一些条目。同样,Iterators和Enumerations返回元素 反映哈希表的状态在某个时刻或之后的某个时刻 创建迭代器/枚举。
答案 1 :(得分:1)
如果在更新数据时未修改地图(即没有放置或删除),则甚至不需要像ConcurrentHashMap这样的同步映射。如果在程序执行期间持续存在put和remove,则需要同步这些调用。但是,即使ConcurrentHashMap在更新频率变高(在多线程程序中)时也会开始抛出ConcurrentModificationExceptions。什么频率太高?您可能需要自己衡量,这取决于您平台中的很多因素。
在这些情况下我做了什么,我尝试创建一种情况,我不必在程序执行期间插入或删除地图,只有在数据流停止时才启动和关闭。如果那是不可能的,我使用普通HashMap和优秀数据结构CopyOnWriteArrayList的组合,并在外部进行同步。我没有测试ConcurrentHashMap的限制,但我不相信它对我自己的生产系统。
编辑:ConcurrentHashMap不会导致任何ConcurrentModificationExceptions,只有在使用Collections.synchronizedMap时才会遇到麻烦。
答案 2 :(得分:1)
一种方法是写入时复制方案,如下所示:
public class Prices {
private volatile Map<String, Integer> prices = Collections.emptyMap();
public void putPrice(String ticker, int price) {
HashMap<String, Integer> newPrices = new HashMap<String, Integer>(prices);
newPrices.put(ticker, price);
prices = newPrices;
}
public Integer getPrice(String ticker) {
return prices.get(ticker);
}
}
这对于获取有一个最小的开销 - 一个从volatile中读取,然后是一个正常的哈希查找。但是,它对于put来说有很大的开销 - 创建一个全新的map,以及写一个volatile。如果您的读写比率很高,这可能仍然是一个很好的权衡。
您可以通过仅在实际需要添加新条目而不是更新现有条目时改变地图来改进这一点;你可以通过使用可变值来实现这一点:
public class Prices {
private volatile Map<String, AtomicInteger> prices = Collections.emptyMap();
public void putPrice(String ticker, int price) {
AtomicInteger priceHolder = prices.get(ticker);
if (priceHolder != null) {
priceHolder.set(price);
}
else {
HashMap<String, AtomicInteger> newPrices = new HashMap<String, AtomicInteger>(prices);
newPrices.put(ticker, new AtomicInteger(price));
prices = newPrices;
}
}
public Integer getPrice(String ticker) {
AtomicInteger priceHolder = prices.get(ticker);
if (priceHolder != null) return priceHolder.get();
else return null;
}
}
我不确定AtomicInteger
的性能特征是什么;它可能比看起来慢。假设AtomicInteger
不是非常慢,这应该非常快 - 它涉及来自volatile的两次读取以及每次获取的正常散列查找,以及来自volatile,哈希查找和单次写入的读取。不稳定以更新现有价格。它仍然涉及复制地图以增加新价格。但是,在典型的市场中,这种情况并不经常发生。