ConcurrentHashMap.get() 是否保证通过不同的线程看到以前的 ConcurrentHashMap.put()?

2022-09-01 11:00:12

ConcurrentHashMap.get() 是否保证通过不同的线程看到以前的 ConcurrentHashMap.put()?我的期望是,阅读JavaDocs似乎表明了这一点,但我99%相信现实是不同的。在我的生产服务器上,以下情况似乎正在发生。(我已经通过日志记录捕获了它。

伪代码示例:

static final ConcurrentHashMap map = new ConcurrentHashMap();
//sharedLock is key specific.  One map, many keys.  There is a 1:1 
//      relationship between key and Foo instance.
void doSomething(Semaphore sharedLock) {
    boolean haveLock = sharedLock.tryAcquire(3000, MILLISECONDS);

    if (haveLock) {
        log("Have lock: " + threadId);
        Foo foo = map.get("key");
        log("foo=" + foo);

        if (foo == null) {
            log("New foo time! " + threadId);
            foo = new Foo(); //foo is expensive to instance
            map.put("key", foo);

        } else
            log("Found foo:" + threadId);

        log("foo=" + foo);
        sharedLock.release();

    } else
        log("No lock acquired");
} 

似乎正在发生的事情是这样的:

Thread 1                          Thread 2
 - request lock                    - request lock
 - have lock                       - blocked waiting for lock
 - get from map, nothing there
 - create new foo
 - place new foo in map
 - logs foo.toString()
 - release lock
 - exit method                     - have lock
                                   - get from map, NOTHING THERE!!! (Why not?)
                                   - create new foo
                                   - place new foo in map
                                   - logs foo.toString()
                                   - release lock
                                   - exit method

所以,我的输出看起来像这样:

Have lock: 1    
foo=null
New foo time! 1
foo=foo@cafebabe420
Have lock: 2    
foo=null
New foo time! 2
foo=foo@boof00boo    

第二个线程不会立即看到 put!为什么?在我的生产系统上,有更多的线程,我只看到一个线程,紧跟在线程1之后的第一个线程,有问题。

我甚至尝试过将 ConcurrentHashMap 上的并发级别缩小到 1,但这并不重要。例如:

static ConcurrentHashMap map = new ConcurrentHashMap(32, 1);

我哪里出错了?我的期望?或者我的代码(真正的软件,而不是上面的软件)中有一些错误导致了这种情况?我已经反复检查了它,并且99%确定我是否正确处理了锁定。我甚至无法理解JVM中的错误。请把我从我自己身上救出来。ConcurrentHashMap

可能相关的戈里细节:

  • 四核 64 位至强 (DL380 G5)
  • RHEL4 ( ...Linux mysvr 2.6.9-78.0.5.ELsmp #1 SMPx86_64 GNU/Linux)
  • Java 6 (,build 1.6.0_07-b0664-Bit Server VM (build 10.0-b23, mixed mode))

答案 1

这里有一些很好的答案,但据我所知,没有人真正为提出的问题提供规范的答案:“ConcurrentHashMap.get()是否保证通过不同的线程看到以前的ConcurrentHashMap.put()”。那些说“是”的人没有提供消息来源。

所以:是的,这是有保证的。(请参阅“内存一致性属性”部分):

将对象放入任何并发集合之前的线程中的操作 - 在另一个线程中从集合中访问或删除该元素之后的操作之前发生。


答案 2

在缓存中创建成本高昂的对象时,基于无法在缓存中找到对象,这是已知问题。幸运的是,这已经得到了实施。

您可以使用Google Collecitons的MapMaker。您只需为它提供一个创建对象的回调,如果客户端代码在映射中查找并且映射为空,则调用回调并将结果放入映射中。

请参阅 MapMaker javadocs ...

 ConcurrentMap<Key, Graph> graphs = new MapMaker()
       .concurrencyLevel(32)
       .softKeys()
       .weakValues()
       .expiration(30, TimeUnit.MINUTES)
       .makeComputingMap(
           new Function<Key, Graph>() {
             public Graph apply(Key key) {
               return createExpensiveGraph(key);
             }
           });

顺便说一句,在您的原始示例中,使用 ConcurrentHashMap 没有任何优势,因为您正在锁定每个访问,为什么不在锁定的部分内使用普通的 HashMap 呢?