2

我写了一个股票市场模拟器,它使用 aConcurrentHashMap作为缓存。

缓存包含大约 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对象。因此伊甸园很快就被填满了。

所以,我正在考虑通过以下方式调整设计:

a)使DataEntry类可变。

b)DataEntry用空对象预先填充缓存。

c)当更新到达时,DataEntry从地图中检索对象并填充字段。

这样,DataEntry对象的数量将是恒定的并且等于元素的数量。

我的问题是:

a)这个设计是否存在我通过使DataEntry可变变量引入的任何并发问题。

b)我还能做些什么来优化缓存吗?

谢谢。

4

4 回答 4

1

听起来您正在使用 aConcurrentHashMap当您真正需要的是并发队列之类的东西时,例如 a LinkedBlockingQueue?

于 2011-12-22T14:31:34.890 回答
1
  • 一种。是的,它确实。可变DataEntry对象可以在没有读者注意的情况下被更新,这将导致不一致的状态。
  • 湾。是的,你可以:制作一个可变的,根据请求DataEntryCache返回不可变的。DataEntry这样,您将DataEntry在读取时创建新对象,而不是在写入时。DataEntryCache可以在内部缓存DataEntry它构造和返回的不可变对象,并在变异调用时使“缓存”无效。

编辑:我假设您缓存的原因(而不是在线程 2 和 3 之间创建队列)是除了线程 2 发送通知的条目之外,消费者线程可能会读取其他条目。如果这个假设不正确,您可能根本不需要缓存。

于 2011-12-22T14:37:23.403 回答
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

这远远超出了您似乎正在使用的访问速率。

如果你想减少垃圾,你可以使用可变对象和原语。出于这个原因,我会避免使用字符串(因为你似乎有比数据条目更多的字符串)

于 2011-12-22T15:36:25.033 回答
0

a)在我的代码中,对象的创建经常表现为瓶颈,所以我认为你自己重用DataEntry对象的想法也值得实现。但是,正如 kdgregory 评论的那样,简单地覆盖当前元素将导致读取不一致的条目。因此,当更新一个条目时,改为写入一个新的或如果可用的话,一个重复使用的空闲(比如空闲几分钟)条目并将其放入映射中。将新条目放入地图后,将旧条目放在某种空闲列表中。为了完全安全,不允许读取线程访问缓存传递的 DataEntry,例如 1 分钟后。如果线程可能阻塞,它们应该复制 DataEntry 对象,也许为此重用自己的对象。

b)您当前的设计是模块化的,但涉及许多上下文切换,因为线程反映了模块。我会尝试一种设计,其中单个请求从开始到完成由单个线程提供服务。请求可以是对新DataEntry对象的完整处理。实现这一点的并发设计模式是Leader/FollowerHalf-Sync/Half-Asynch

于 2011-12-22T14:39:58.600 回答