我写了一个股票市场模拟器,它使用ConcurrentHashMap
作为缓存。
缓存可容纳大约75个元素,但它们可以非常快速地更新和检索(每秒约500次)。
这是我做的:
主题1:
连接到外部系统,为我提供给定股票代码的流媒体报价。
线程2(回调线程):
等待外部系统将数据传送给它。一旦获取数据,它就会解析它,创建一个不可变的DataEntry对象,缓存它并向thread3发送一个信号。
主题3(消费者主题): 收到信号后,从缓存中检索DataEntry并使用它。 (不让thread2直接将数据推送到thread3,这是任务的一部分。)
public final class DataEntry{
private final String field1;
private final String field2;
//...
private final String field25;
// Corresponding setters and getters
}
public final class Cache{
private final Map<String, DataEntry> cache;
public Cache( ){
this.cache = new ConcurrentHashMap<String, DataEntry> ( 65, 0.75, 32 );
}
// Methods to update and retrieve DataEntry from the cache.
}
通过分析器运行后,我注意到我正在创建DataEntry
对象的 lot 。因此,伊甸园很快就会填满。
所以,我想通过以下方式调整设计:
a)使DataEntry
类可变。
b)使用空DataEntry
个对象预先填充缓存。
c)更新到货时,从地图中检索DataEntry
对象并填充字段。
这样,DataEntry
对象的数量将是常量并且等于元素的数量。
我的问题是:
a)此设计是否存在因使DataEntry
变为可变而引入的并发问题。
b)我还能做些什么来优化缓存吗?
感谢。
答案 0 :(得分:1)
听起来你正在使用ConcurrentHashMap
当你真正需要的东西就像一个并发队列 - 比如一个LinkedBlockingQueue
?
答案 1 :(得分:1)
DataEntry
个对象,这将导致状态不一致。DataEntryCache
,根据请求返回不可变DataEntry
。这样,您将在读取时创建新的DataEntry
对象,而不是写入。 DataEntryCache
可以在内部缓存它构造和返回的不可变DataEntry
,并使变异调用上的“缓存”无效。编辑:我假设您缓存的原因(与在线程2和3之间创建队列相反)是消费者线程可以读取除线程2发送通知之外的其他条目。如果这个假设不正确,您可能根本不需要缓存。
答案 2 :(得分:1)
我不担心ConcurrentHashMap的速度
Map<Integer, Integer> map = new ConcurrentHashMap<>();
long start = System.nanoTime();
int runs = 200*1000*1000;
for (int r = 0; r < runs; r++) {
map.put(r & 127, r & 127);
map.get((~r) & 127);
}
long time = System.nanoTime() - start;
System.out.printf("Throughput of %.1f million accesses per second%n",
2 * runs / 1e6 / (time / 1e9));
打印
Throughput of 72.6 million accesses per second
这远远超出您使用的访问率。
如果你想减少垃圾,你可以使用可变对象和原语。出于这个原因,我会避免使用String(因为你似乎拥有比数据条目多得多的字符串)
答案 3 :(得分:0)
a)在我的代码中,对象创建通常表现为瓶颈,所以我认为你自己重用DataEntry
对象的想法也值得实现。但是,正如kdgregory所评论的那样,简单地覆盖当前元素将导致读取不一致的条目。因此,在更新条目时,请写入新的或可用的重用空闲(比如空闲几分钟)条目并将其放入映射中。将新条目放入映射后,将旧条目放在某种空闲列表中。为了完全安全,在读取线程之后,不得允许读取线程访问缓存所传递的DataEntry。 1分钟。如果线程可能阻塞,它们应该复制DataEntry对象,也许重用自己的对象。
b)您当前的设计是模块化的,但涉及许多上下文切换,因为线程反映了模块。我会尝试一种设计,其中单个请求从开始到完成由单个线程提供。请求可以是对新DataEntry
对象的完整处理。实现此目标的并发设计模式为Leader/Follower和Half-Sync/Half-Asynch。