【问题标题】:Java: Using ConcurrentHashMap as a lock managerJava:使用 ConcurrentHashMap 作为锁管理器
【发布时间】:2014-01-17 04:29:03
【问题描述】:

我正在编写一个高度并发的应用程序,需要访问大量细粒度的共享资源。我目前正在编写一个全局锁管理器来组织它。我想知道我是否可以背负标准ConcurrentHashMap 并使用它来处理锁定?我正在考虑一个类似如下的系统:

  • 单个全局 ConcurrentHashMap 对象包含资源的唯一字符串 id 与 保护该资源的锁使用该资源的线程的唯一 id 之间的映射
  • 调整并发因子以反映对高并发性的需求
  • 使用 hashmap 中的原子条件 replace(K key, V oldValue, V newValue) 方法获取锁
  • 为防止在锁定多个资源时发生锁争用,必须按字母顺序获取锁

设置是否存在任何重大问题?表现会如何?

我知道这可能会比一个正确编写的锁定系统慢得多,而且内存量更大,但我宁愿不花几天时间尝试编写自己的锁定系统,特别是考虑到我可能无法编写自己的锁定系统匹配 Java 专业编写的实现映射的并发代码。

另外,我从来没有在高负载情况下使用过 ConcurrentHashMap,所以我对以下内容感兴趣:

  • 这对大量元素的扩展效果如何? (我认为 ~1,000,000 是一个不错的上限。如果超过这个上限,我愿意更有效地重写它)
  • 文档指出,重新调整大小“相对”较慢。到底有多慢?我可能必须每分钟左右重新调整一次地图的大小。这会对我正在查看的地图大小造成问题吗?

编辑:感谢 Holger 指出 HashMaps 在缩放方面不应该有那么大的问题

另外,有没有更好/更标准的方法?我找不到任何使用这种系统的地方,所以我猜测要么我没有看到重大缺陷,要么还有其他问题。


编辑:

我正在编写的应用程序是一个网络服务,它处理可变数量的请求。我正在使用 Grizzly 项目来平衡多个线程之间的请求。 每个请求都使用少量的共享资源(~30),所以一般来说,我预计不会有很大的争用。请求通常在 500 毫秒内完成对资源的处理。因此,我会接受一些阻塞/连续轮询,因为请求不是对时间非常敏感,并且争用应该是最小的。

一般来说,看到正确的解决方案与 ConcurrentHashMap 在幕后的工作方式非常相似,我想知道我是否可以安全地使用它作为快捷方式,而不是编写/调试/测试我自己的版本。

【问题讨论】:

  • 我刚刚在 Google Guava 中发现了 Striped,这就是我想要的一切。
  • 条纹可能仍然是内存密集型。在这里查看我的旅程:stackoverflow.com/q/39675003/194609

标签: java multithreading concurrency locking concurrenthashmap


【解决方案1】:

调整大小的问题不相关,因为您已经告诉了问题中元素数量的估计值。因此,您可以为 ConcurrentHashMap 提供足够大的初始容量以避免任何重新散列。

性能不取决于元素的数量,这是散列的主要目标,而是并发线程的数量。

主要问题是您没有计划如何处理失败的锁。除非您想轮询直到锁定成功(不推荐),否则您需要一种使线程进入睡眠状态的方法,这意味着当前拥有锁的线程必须在释放时唤醒睡眠线程(如果存在)。所以你最终需要Lock 不提供的传统ConcurrentHashMap 功能。

为每个元素创建一个 Lock(如您所说的 ~1,000,000)不是解决方案。


解决方案看起来有点像 ConcurrentHashMap 在内部工作。给定特定的并发级别,即您可能拥有的线程数(向上取整),您创建的 Locks 数量(远小于 1,000,000)。

现在您为每个元素分配Locks 之一。一个简单的分配将基于元素的hashCode,假设它是稳定的。然后锁定一个元素意味着锁定分配的Lock,如果所有当前锁定的元素都映射到不同的Locks,则可以达到配置的并发级别。

这可能意味着如果元素映射到相同的Lock,锁定不同元素的线程会相互阻塞,但可能性是可预测的。您可以尝试微调并发级别(如上所述,使用高于线程数的数字)以找到最佳权衡。

这种方法的一大优点是您不需要维护依赖于元素数量的数据结构。 Afaik,新的并行 ClassLoader 使用了类似的技术。

【讨论】:

  • 我编辑了我的帖子,提供了有关该问题的更多信息。关于您的解决方案:当我说资源ID和锁之间会有映射时,我意识到我应该澄清一下。我指的不是实际的Lock 对象,而只是使用资源的线程的标识符,可能只是唯一的 Thread.getID 编号。因此,不应创建 1,000,000 个Lock 对象,而应仅创建几个Longs。感谢您的建议。我会再等一两天,看看是否还有其他问题,否则我会将其标记为答案。
  • @RandomBK:我不认为你指的是实际的Locks。我解释说你不能没有Locks,因为当两个线程试图锁定同一个元素时,acquire 失败的情况下,你需要某种等待队列。 ConcurrentHashMap 不提供此功能,无论您如何解决此问题,拥有一百万个等待队列是不够的。这是我的提议。使用现有的Locks 来维护状态和等待队列。但不是一百万。
  • 关于您更新的问题:如果轮询正常,您可以使用哈希映射方法,但可以简化它:没有理由记住未锁定的元素。所以你可以使用putIfAbsent和remove来控制锁定状态。那么地图的大小将仅等于 锁定 元素的数量,这将远小于元素的总数。
  • 感谢您的建议!
猜你喜欢
  • 1970-01-01
  • 2021-07-30
  • 2019-12-05
  • 1970-01-01
  • 1970-01-01
  • 2013-05-06
  • 2017-06-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多