【问题标题】:Concurrency Issue with HashMap and ReentrantLockHashMap 和 ReentrantLock 的并发问题
【发布时间】:2021-10-01 06:27:27
【问题描述】:

我有一段代码在启动时创建了一个 ReentrantLock 键的 HashMap。

void constructor() {
        this.lockMap = new HashMap<>();
        for (int i=0; i<100; i++) {
             this.lockMap.put(i, new ReentrantLock(true));
        }
}

在并发执行期间,我尝试通过以下方式锁定lockMap内部的锁:

runConcurrently() {
         ii = 10;
         if (!lockMap.containsKey(ii)) {
                log.error("lock id is not found in the lockMap " + ii);
         }
        
         locked = lockMap.get(ii).tryLock();
         if (!locked) {
             return;
         }
         runCriticialSection();
         lockMap.get(ii).unlock();
    }

    void runCriticialSection() {
       log.info("hello");
       log.info("I'm here");                                                      
    }

所以这是我在代码运行时每 4 小时看到一次的情况,这种情况非常罕见。

我看到了这些日志:

你好。
你好。
我在这。
我在这。

然后我在第三次访问同一键 ii =10 上的 hasmap 后立即看到此日志:

在地图 10 中找不到锁 ID。
NullPointerException ... 试图访问地图。

我应该在保证订购中看到的位置:

你好。
我在这。
你好。
我在这。

Hashmap 在执行期间根本不会被修改。

hashmap不是并发hashmap有问题吗?是获取,而不是在没有修改的情况下线程安全?由于并发hasmap的锁定速度很慢,我特别不使用它。但是 hashmap 只在启动时创建,之后永远不会修改。我觉得这很奇怪,似乎锁已被获取了两次,而且地图中似乎缺少该元素。

【问题讨论】:

  • 您需要在记录未找到错误后从第一个 if 语句返回。这应该是 NullPointerException 的原因。我没有看到你在任何地方释放锁,但我认为你这样做了?另外,您检查 ii,然后使用 lockId。请发布您的实际代码,这不是完整的故事吗?
  • 抱歉,我添加了其余代码。我添加了日志记录以确保密钥始终在地图中,因为地图从未被修改过。 NPE 永远不应该发生。
  • 您应该在构造函数中初始化地图,而不是在方法void constructor() 中。我们不知道何时以及多久调用此方法。此外,runConcurrently() 中没有变量声明。这意味着要么您在此处使用字段,受竞争条件的影响,要么您发布了虚构的伪代码而不是实际代码。似乎是后者,因为返回类型也丢失了,您已经不得不在稍后提交代码的关键部分。因此,我投票结束,因为我们无法在不查看代码的情况下告诉您代码有什么问题。

标签: java concurrency


【解决方案1】:

地图本身没有并发问题,如果地图在构造函数之后从未修改过。如果是这样,线程将只能看到地图的最终版本。否则,行为未定义。

临界区不能独占访问

从您的输出看来,(至少)两个线程同时访问了runCriticialSection

这是因为您为ii 的每个值使用了不同的锁。一个锁只排除另一个线程锁定它,如果其他线程使用相同的锁!因此,不使用 ii 相同值的线程将毫不费力地同时运行 runCriticialSection。这可能会导致如上所示的输出异常,如下所示:

  1. 线程 1 执行log.info("hello");
  2. 线程 2 执行 log.info("hello");
  3. 线程 1 执行 log.info("I'm here");
  4. 线程 2 执行 log.info("I'm here");

如果您想独占访问某个部分,请始终在该部分周围使用相同的锁

编码问题

当检查ii 映射到锁失败时,您不应继续,而是返回或抛出异常。如果不这样做,locked = lockMap.get(ii).tryLock(); 会抛出 NullPointerExcetpion,因为 lockMap.get(ii) 返回 null

在锁定和解锁之间,您正在运行用户代码,格式为runCriticalSection。如果您稍后更改该方法的实现并且它开始扔东西:您的锁永远不会解锁!始终使用带锁的 try ... finally

解决这些问题,可能会导致以下代码:

if (!lockMap.containsKey(ii)) {
    log.error("lock id is not found in the lockMap " + ii);
    return;
}
locked = lockMap.get(ii).tryLock();
if (!locked) {
    return;
}
try {
    runCriticialSection();
}
finally {
    lockMap.get(ii).unlock();
}

实际上,我只是将锁放在局部变量中,但这是一个见仁见智的问题。

ReentrantLock lock = lockMap.get(ii);
if (lock == null) {
    log.error("lock id is not found in the lockMap " + ii);
    return;
}
locked = lock.tryLock();
if (!locked) {
    return;
}
try {
    runCriticialSection();
}
finally {
    lock.unlock();
}

【讨论】:

    猜你喜欢
    • 2010-11-03
    • 1970-01-01
    • 1970-01-01
    • 2012-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多