Java 单写多读原语映射
我正在尝试用Java编写一个actor实现。我的设计需要一个高性能的map数据结构,用于查找特定参与者计划在哪个线程上。查找是使用int id完成的。所有参与者都有单独的id。我有以下要求: i) 键是基本整数,而不是整数类 ii)值也是原语。值只能包含在数据结构实例化之前已知的几个数字。值只是线程/核心的id,因此它可以是短的。线程数小于机器上的内核数,因此不能达到非常高的数目 iii)地图由单线程写入,但从多线程读取。我希望我的实现是无锁的,没有任何共享(false或其他)。因此,读取不应涉及对非线程本地内存的任何写入 iv)使用map进行查找的多个读卡器线程的读取将大大超过写入(通过单个线程)的数量 v) 所需的主要操作包括:Java 单写多读原语映射,java,c++,multithreading,c++11,Java,C++,Multithreading,C++11,我正在尝试用Java编写一个actor实现。我的设计需要一个高性能的map数据结构,用于查找特定参与者计划在哪个线程上。查找是使用int id完成的。所有参与者都有单独的id。我有以下要求: i) 键是基本整数,而不是整数类 ii)值也是原语。值只能包含在数据结构实例化之前已知的几个数字。值只是线程/核心的id,因此它可以是短的。线程数小于机器上的内核数,因此不能达到非常高的数目 iii)地图由单线程写入,但从多线程读取。我希望我的实现是无锁的,没有任何共享(false或其他)。因此,读取不应涉
Set(key,value)
和delete(key,value)
始终从单个writer线程调用。大多数设置的键最终也会被删除,所以在多次删除后性能不会下降。新的键(参与者ID)将使用递增的整数生成,并表示参与者的创建。删除密钥(参与者id)表示该参与者已退出,并且永远不会复活。这也意味着一个键一旦被删除,就再也不会被设置。重要的是,我们不要在映射中积累死空间,因为将发生删除(参与者退出)Get(key)
从读卡器线程调用get(key)
需要最终保持一致
,但需要注意一些事项。假设writer线程已将对key1->value1更改为key1->value2。如果其中一个读卡器执行get(键1)并仍然接收value1,则这不是问题。最终它应该会变得有价值。如果writer线程删除了对key1->value1,并且读取器线程上的get(key1)仍然返回value1,也可以。实际上,我的意思是Java的putOrderedObject/lazySet/getObjectVolatile
,或者C++11的std::memory\u order\u released/std::memory\u order\u acquire/std::memory\u order\u release
可以合并。另一方面,如果确实设置了值,get(key1)
不应返回空值(比如-1)。如果get(key1)
返回空值以满足此要求,我可以调用getStrict(key1)
操作
我不直接使用图书馆的原因有:
i) Java集合:它们需要包装类,因此无法满足我的目标(i)和(ii)
ii)Trove、FastUtil等:它们有原始地图,但不提供任何并发访问设施。它们也不会针对稀疏范围内的值进行优化—在我的例子中,核心数是多少
iii)Java ConcurrentHashMap/ConcurrentSkipListMap:它们需要包装类,并且不针对单个编写器、多个读取器用例进行优化
我意识到这些要求很多,因此,如果答案解决了其中一些问题,而对其他一些问题仍然模棱两可,那就好了。给我指点设计的源代码或注释会很好。任何关于取舍的解释都是额外的好处,因为我正在努力学习如何捕鱼
如果我把我的详细要求归结为几个问题,它们可能是:
i) 如何针对单个编写器/多个读取器用例进行优化
ii)如何设计get(key)
和getStrict(key)
操作?这是正确的思考方式吗
iii)如何设计地图以利用递增键和稀疏值范围
iv)我如何以最佳方式处理频繁删除?任何大小调整/重新灰化都需要对读卡器线程立即可见,而不是最终可见
此外,欢迎使用C++/C++11代码回答任何问题。通过一些研究,我应该能够将大多数std::atomic操作转换为Java不安全操作。错误共享只来自多个编写器,因为您只有一个编写器,所以编写器之间的共享不应该有问题 i) 如何针对单个编写器/多个读取器用例进行优化 您不需要为多个读卡器执行任何特殊操作,在这种情况下,每个线程都有一个数据结构的本地副本。单个编写器是最简单(也是最快)的用例 因此,Trove和ConcurrentMaps都可以很好地实现这一点。顺便说一句,ConcurrentMap还针对多个编写器进行了优化 ii)如何设计get(key)和getStrict(key)操作?这是正确的思考方式吗 您描述的是并发集合现在是如何工作的。我不清楚getStrict有什么不同之处 iii)如何设计地图以利用递增键和稀疏值范围 如果您有普通的递增键,也许环形缓冲区是更好的选择。如果您有
稀疏值
则只需存储该值
iv)我如何以最佳方式处理频繁删除
根据您的操作,环形缓冲区对于删除非常有效。要考虑的主要问题是有一个内存/对象循环策略。这将减少重新分配和垃圾收集的成本
任何大小调整/重新灰化都需要对读卡器线程立即可见,而不是最终可见
如果值最终可以是c