简体   繁体   English

HashMap 和 ReentrantLock 的并发问题

[英]Concurrency Issue with HashMap and ReentrantLock

I have a piece of code that on startup creates a HashMap of key to ReentrantLock.我有一段代码,它在启动时创建了 ReentrantLock 键的 HashMap。

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

During concurrent execution, I try to lock the lock inside the lockMap in the following manner:在并发执行期间,我尝试通过以下方式锁定 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");                                                      
    }

so here is what I have seen happen once in while every 4 hours the code is running, in a very rare occurrence.所以这是我在代码每 4 小时运行一次时看到的情况,这种情况非常罕见。

I see these logs:我看到这些日志:

hello.你好。
hello.你好。
I'm here.我在这。
I'm here.我在这。

and then I see this log right after on third time accessing the hasmap on the same key ii =10:然后我在第三次访问同一键 ii =10 上的 hasmap 后立即看到此日志:

lock id is not found in the map 10.在地图 10 中找不到锁 ID。
NullPointerExeception ... trying to access the map. NullPointerException ... 试图访问地图。

where I should see in guaranteed ordering:我应该在保证订购的地方看到:

hello.你好。
I'm here.我在这。
hello.你好。
I'm here.我在这。

The Hashmap never gets modified during execution at all. Hashmap 在执行过程中根本不会被修改。

is there an issue with hashmap not being concurrent hashmap? hashmap不是并发hashmap有问题吗? is get, not threadsafe in absence of modifications?在没有修改的情况下是get,不是线程安全的? I am specifically not using it due to locking slowness in concurrent hasmap.由于并发 hasmap 中的锁定缓慢,我特别不使用它。 But the hashmap is only created on startup and never modified after.但是 hashmap 只在启动时创建,之后就不再修改。 I find it very weird where it seems the lock has been acquired twice and it seems like the element is missing from the map.我觉得很奇怪,似乎锁已被获取两次,并且地图中似乎缺少该元素。

There is no concurrency issue with the map itself, if the map is never modified after the constructor .如果在构造函数之后从未修改地图,地图本身没有并发问题。 If so, threads will only ever see that final version of the map.如果是这样,线程将只会看到地图的最终版本。 Else, the behaviour is undefined.否则,行为是未定义的。

No exclusive access of the critical section没有对临界区的独占访问

From your output, it appears that (at least) two threads accessed runCriticialSection simultaneously.从您的输出看来,(至少)两个线程同时访问了runCriticialSection

This is due to the fact that you are using a different lock for each value of ii .这是因为您对ii每个值使用不同的锁。 A lock only excludes another thread from locking it, if that other threads uses that same lock!如果其他线程使用相同的锁,锁只会将另一个线程排除在锁定之外 Thus, threads that do not use the same value of ii , will effortlessly run runCriticialSection simultaneously.因此,不使用相同ii值的线程将毫不费力地同时运行runCriticialSection That can result in the described output anomaly as shown above, as follows:这可能导致上述输出异常,如下所示:

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

If you want exclusive access to a section, always use the same lock surrounding that section .如果您想独占访问某个部分,请始终使用围绕该部分的相同锁

Coding problems编码问题

When the check fails that ii maps to a lock, you should not continue but instead return or throw an exception.ii映射到锁的检查失败时,您不应继续,而是返回或抛出异常。 If you don't, locked = lockMap.get(ii).tryLock();如果不这样做, locked = lockMap.get(ii).tryLock(); throws a NullPointerExcetpion , because lockMap.get(ii) returns null .抛出NullPointerExcetpion ,因为lockMap.get(ii)返回null

Between locking the lock and unlocking it, you are running user code, in the form of runCriticalSection .在锁定和解锁之间,您正在以runCriticalSection的形式运行用户代码。 If you change the implementation of that method later and it starts throwing things: your lock will never unlock!如果您稍后更改该方法的实现并且它开始抛出东西:您的锁将永远不会解锁! Always use try ... finally with a lock.总是使用try ... finally带锁。

Fixing these issues, could lead to the following code:修复这些问题,可能会导致以下代码:

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();
}

Actually, I would just put the lock in a local variable, but that is a matter of opinion.实际上,我只是将锁放在一个局部变量中,但这是一个见仁见智的问题。

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();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM