流式数据的理想Java数据结构
我脑子里有一个特定的用例,但无法找出正确的数据结构来使用 我有一个线程将对象流式传输到HashMap中。类似于市场数据,你有一个很高的未知频率的滴答声 另一个线程,它不断地读取此地图以获取更新的价格对象,并按键进行查询,没有特定的顺序。在给定的周期内,对同一密钥的查询可能会多次。读取和写入非常频繁,但读取线程只对完全更新的最新可用数据感兴趣,在写入完成之前不一定阻塞 我想听听您对此类用例的理想数据结构的想法。有没有比ConcurrentHashMap更好的实现 谢谢。来自Javadoc 一种支持完全并发的检索和调整的哈希表 更新的预期并发性。这个类遵循相同的函数 规范为哈希表,并包括方法的版本 对应于哈希表的每个方法。然而,即使所有 操作是线程安全的,检索操作不需要 锁定,并且不支持在中锁定整个表 阻止所有访问的方法。此类可与完全互操作 程序中的哈希表依赖于它的线程安全性,但不依赖于它的 同步细节 检索操作(包括get)通常不会阻塞,因此 与更新操作重叠(包括放置和删除)。检索 反映最近完成的更新操作的结果 坚持他们的开始。用于聚合操作,如putAll和 清除、并发检索可能只反映插入或删除 一些条目。类似地,迭代器和枚举返回元素 反映哈希表在 创建迭代器/枚举流式数据的理想Java数据结构,java,performance,collections,Java,Performance,Collections,我脑子里有一个特定的用例,但无法找出正确的数据结构来使用 我有一个线程将对象流式传输到HashMap中。类似于市场数据,你有一个很高的未知频率的滴答声 另一个线程,它不断地读取此地图以获取更新的价格对象,并按键进行查询,没有特定的顺序。在给定的周期内,对同一密钥的查询可能会多次。读取和写入非常频繁,但读取线程只对完全更新的最新可用数据感兴趣,在写入完成之前不一定阻塞 我想听听您对此类用例的理想数据结构的想法。有没有比ConcurrentHashMap更好的实现 谢谢。来自Javadoc 一种支持
如果在更新数据时没有修改映射(即没有放置或删除),那么您甚至不需要像ConcurrentHashMap这样的同步映射。如果在程序执行过程中不断有put和remove,则需要同步这些调用。但是,即使是ConcurrentHashMap,当更新频率达到较高时(即在多线程程序中),也会开始抛出ConcurrentModificationException。什么频率太高?您可能需要自己进行测量,这取决于平台中的许多因素 在这些情况下,我会尝试创建一种情况,在程序执行期间,我不必从映射中插入或删除,只有在数据流停止时启动和关闭。如果这是不可能的,我将使用普通HashMap和优秀的数据结构的组合,并在外部进行同步。我还没有测试ConcurrentHashMap的限制,但我不相信它能用于我自己的生产系统
编辑:ConcurrentHashMap仅当您使用Collections.synchronizedMap时,才不会导致任何ConcurrentModificationException。您可能会遇到麻烦。一种方法是写时拷贝方案,如下所示:
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);
}
}
公共类价格{
private volatile Map prices=Collections.emptyMap();
公共价格(字符串代码、整数价格){
HashMap newPrices=新HashMap(价格);
newPrices.put(股票代码,价格);
价格=新价格;
}
公共整数getPrice(字符串代码){
返回价格。获取(股票代码);
}
}
对于gets来说,这有一个最小的开销——从一个volatile读取一次,然后是一个普通的哈希查找。然而,它有一个巨大的put开销——创建一个全新的映射,再加上对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;
}
}
公共类价格{
private volatile Map prices=Collections.emptyMap();
公共价格(字符串代码、整数价格){
AtomicInteger价格持有者=prices.get(股票代码);
if(priceHolder!=null){
价格持有者。设置(价格);
}
否则{
HashMap newPrices=新HashMap(价格);
newPrices.put(股票代码,新原子整数(price));
价格=新价格;
}
}
公共整数getPrice(字符串代码){
AtomicInteger价格持有者=prices.get(股票代码);
if(priceHolder!=null)返回priceHolder.get();
否则返回null;
}
}
我不确定原子整数的性能特征是什么;这可能比看起来慢。假设
AtomicInteger
的速度不是不合理的慢,这应该是相当快的-它包括两次读取volatile加上每个get的正常哈希查找,以及一次读取volatile、一次哈希查找和一次写入volatile以更新现有价格。它仍然需要复制地图以增加新的价格。然而,在一个典型的市场中,这种情况并不经常发生。在更新勾号数据时,是否会修改hashmap(即是否会有put和remove)?或者映射将在数据开始进入之前设置?是的,将有大量的PUT,但没有删除。基本上,在每一个到达的滴答声中,我都会做一个看跌期权(键,价格)。另一方面,我也可以使用一些虚拟对象预填充hashMap,因为我之前就知道这些键。请您解释一下,ConcurrentHashMap会引发什么样的异常,以及在什么情况下会引发什么样的高更新频率。我在适当的情况下也使用了CopyOnWriteArrayList。我的问题是关于您的陈述“然而,即使是ConcurrentHashMap在更新时也会开始抛出ConcurrentModificationException