简体   繁体   English

更新ConcurrentHashMap中的其他键的后果#computeIfAbsent

[英]Consequences of updating other key(s) in ConcurrentHashMap#computeIfAbsent

Javadoc from ConcurrentHashMap#computeIfAbsent says 来自ConcurrentHashMap#computeIfAbsent Javadoc ConcurrentHashMap#computeIfAbsent

The computation should be short and simple, and must not attempt to update any other mappings of this map. 计算应该简短,并且不得尝试更新此映射的任何其他映射。

But, from what I see, using remove() and clear() methods inside mappingFunction works fine. 但是,从我看到的,在mappingFunction使用remove()clear()方法工作正常。 For example this 例如这个

Key element = elements.computeIfAbsent(key, e -> {
    if (usages.size() == maxSize) {
        elements.remove(oldest);
    }
    return loader.load(key);
});

What bad consequences of using remove() method inside mappingFunction could be? mappingFunction中使用remove()方法有什么不好的后果呢?

Here's an example of a bad consequence: 这是一个不好后果的例子:

ConcurrentHashMap<Integer,String> cmap = new ConcurrentHashMap<> ();
cmap.computeIfAbsent (1, e-> {cmap.remove (1); return "x";});

This code causes a deadlock. 此代码导致死锁。

The javadoc explains clearly the cause : javadoc清楚地解释了原因:

Some attempted update operations on this map by other threads may be blocked while computation is in progress , so the computation should be short and simple, and must not attempt to update any other mappings of this map. 其他线程在此映射上的某些尝试更新操作可能 在计算 进行时 被阻止 ,因此计算应该简短,并且不得尝试更新此映射的任何其他映射。

You have not to forget that ConcurrentHashMap is designed to provide a way of using a thread safe Map without being as locking as it is the case for older thread safe Map classes as HashTable . 您不必忘记ConcurrentHashMap旨在提供一种使用线程安全Map的方法,而不是像旧HashTable那样将旧线程安全Map类作为锁定。
When a modification on the map occurs, it locks only the concerned mapping and not the whole map. 当对地图进行修改时,它仅锁定相关的映射而不是整个映射。

ConcurrentHashMap is a hash table supporting full concurrency of retrievals and high expected concurrency for updates. ConcurrentHashMap是一个哈希表,支持检索的完全并发和更新的高预期并发性。

computeIfAbsent() is a new method added in Java 8. computeIfAbsent()是Java 8中添加的新方法。
If badly used, that is, if in the body of computeIfAbsent() that already locks the mapping of the key passed to the method, you lock another key, you enter in a path where you may defeat the purpose of the ConcurrentHashMap as finally you will lock two mapping. 如果使用computeIfAbsent() ,也就是说,如果在computeIfAbsent()的主体中已经锁定了传递给方法的键的映射,则锁定另一个键,你输入一个路径,你可能会失去ConcurrentHashMap的目的,最后你将锁定两个映射。
Imagine the problem if you lock more mapping inside computeIfAbsent() and that the method is not short at all. 想象一下,如果你在computeIfAbsent()锁定更多的映射并且该方法根本不短,那么问题就出现了。 Concurrency access on the map would become slow. 地图上的并发访问会变慢。

So the javadoc of computeIfAbsent() stresses on this potential issue by reminding the principles of ConcurrentHashMap : keep it simple and fast. 因此, computeIfAbsent()的javadoc通过提醒ConcurrentHashMap的原理来强调这个潜在的问题:保持简单和快速。


Here is example code illustrating the issue. 以下是说明问题的示例代码。
Suppose we have an instance of ConcurrentHashMap<Integer, String> . 假设我们有一个ConcurrentHashMap<Integer, String>的实例。

We will start two threads that use it : 我们将启动两个使用它的线程:

  • The first thread : thread1 that invokes computeIfAbsent() with the key 1 第一个线程: thread1 ,使用键1调用computeIfAbsent()
  • The second thread : thread2 that invokes computeIfAbsent() with the key 2 第二个线程: thread2 ,使用键2调用computeIfAbsent()

thread1 executes a fast enough task but it doesn't follow advise of the computeIfAbsent() javadoc : it updates the key 2 in computeIfAbsent() , that is another mapping of which one used in the current context of the method (that is key 1 ). thread1执行足够快的任务,但它不遵循的提醒computeIfAbsent()的Javadoc:它更新键2computeIfAbsent()即其中一个的另一映射在方法(的当前上下文中使用是关键1 )。

thread2 executes a long enough task. thread2执行足够长的任务。 It invokes computeIfAbsent() with the key 2 by following advises of the javadoc : it doesn't update any other mapping in the implementation of it. 它通过遵循javadoc的建议来调用带有键2computeIfAbsent() :它不会更新它的实现中的任何其他映射。
To simulate the long task, we can use the Thread.sleep() method with 5000 as parameter. 为了模拟长任务,我们可以使用Thread.sleep()方法和5000作为参数。

For this specific situation, if thread2 starts before thread1 , the invocation of map.put(2, someValue); 对于这种特定情况,如果thread2thread1之前thread1 ,则调用map.put(2, someValue); in thread1 will be blocked while thread2 is not returned of computeIfAbsent() that locks the mapping of the key 2 . thread1而将被阻塞thread2不返回的computeIfAbsent()用于锁定的键的映射2

Finally, we get a ConcurrentHashMap instance that blocks the mapping of the key 2 during 5 seconds while computeIfAbsent() is invoked with the mapping of the key 1 . 最后,我们得到一个ConcurrentHashMap实例,它在5秒内computeIfAbsent()2的映射,同时使用键1的映射调用computeIfAbsent()
It is misleading, not effective and it goes against the ConcurrentHashMap intention and the computeIfAbsent() description which the intention is computing the value for the current key : 它具有误导性,无效并且与ConcurrentHashMap意图和computeIfAbsent()描述computeIfAbsent() ,意图是计算当前键的值:

if the specified key is not already associated with a value, attempts to compute its value using the given mapping function and enters it into this map unless null 如果指定的键尚未与值关联,则尝试使用给定的映射函数计算其值并将其输入此映射,除非null

The sample code : 示例代码:

import java.util.concurrent.ConcurrentHashMap;

public class BlockingCallOfComputeIfAbsentWithConcurrentHashMap {

  public static void main(String[] args) throws InterruptedException {
    ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();

    Thread thread1 = new Thread() {
        @Override
        public void run() {
            map.computeIfAbsent(1, e -> {
                String valueForKey2 = map.get(2);
                System.out.println("thread1 : get() returns with value for key 2 = " + valueForKey2);
                String oldValueForKey2 = map.put(2, "newValue");
                System.out.println("thread1 : after put() returns, previous value for key 2 = " + oldValueForKey2);
                return map.get(2);
            });
        }
    };

    Thread thread2 = new Thread() {
        @Override
        public void run() {
          map.computeIfAbsent(2, e -> {
            try {
              Thread.sleep(5000);
            } catch (Exception e1) {
              e1.printStackTrace();
            }
            String value = "valueSetByThread2";
            System.out.println("thread2 : computeIfAbsent() returns with value for key 2 = " + value);
            return value;
          });
        }
    };

    thread2.start();
    Thread.sleep(1000);
    thread1.start();
  }
}

As output we always get : 作为输出,我们总是得到:

thread1 : get() returns with value for key 2 = null thread1:get()返回值2的值= null

thread2 : computeIfAbsent() returns with value for key 2 = valueSetByThread2 thread2:computeIfAbsent()返回key 2 = valueSetByThread2的值

thread1 : after put() returns, previous value for key 2 = valueSetByThread2 thread1:put()返回后,key 2的前一个值= valueSetByThread2

This is written fast as reads on the ConcurrentHashMap are not blocking : 这是快速编写的,因为ConcurrentHashMap上的读取没有阻塞:

thread1 : get() returns with value for key 2 = null thread1:get()返回值2的值= null

but this : 但是这个 :

thread1 : after put() returns, previous value for key 2 = valueSetByThread2 thread1:put()返回后,key 2的前一个值= valueSetByThread2

is output only when thread2 is returned of computeIfAbsent() . 仅当threadI返回computeIfAbsent()时才输出。

Such advice is a bit like the advice not to walk down the middle of a road. 这样的建议有点像不走在路中间的建议。 You can do it, and you may well not be hit by a car; 你可以做到,你可能不会被汽车击中; you may also be able to get out of the way if you see the car coming. 如果你看到汽车即将到来,你也可以走开。

However, you'd have been safer if you'd just stayed on the pavement (sidewalk) in the first place. 但是,如果你刚刚在人行道(人行道)上停留,那么你会更安全。

If an API document tells you not to do something, there is of course nothing stopping you from doing it. 如果API文档告诉您不要做某事,那么当然没有什么能阻止您这样做。 And you might try doing it, and find that there are no ill consequences, at least in the limited circumstances that you test. 你可能会尝试这样做,并发现没有不良后果,至少在你测试的有限环境中。 You can even dig in to find out the exact reasons why the advice is there; 您甚至可以深入了解建议的确切原因; you can scrutinize the source code and prove that it is safe in your use case. 您可以仔细检查源代码并证明它在您的用例中是安全的。

However, the implementors of the API are free to change the implementation, within the constraints of the contract described by the API documentation. 但是,API的实现者可以在API文档描述的合同约束内自由更改实现。 They might make a change that stops your code working tomorrow, because they are under no obligation to preserve behavior that they explicitly warn against using. 他们可能会做出改变,阻止您明天的代码工作,因为他们没有义务保留他们明确警告不要使用的行为。

So, to answer your question of what the bad consequences could be: literally anything (well, anything that completes normally or throws a RuntimeException ); 所以,回答你的问题是什么可能是坏的结果:字面上任何东西(好吧,任何正常完成或抛出RuntimeException ); and you would not necessarily observe the same consequences over time or on different JVMs. 并且您不一定会在一段时间内或在不同的JVM上观察到相同的后果。

Stay on the pavement: don't do what the documentation tells you not to. 留在人行道上:不要做文件告诉你的事情。

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

相关问题 ConcurrentHashMap computeIfAbsent - ConcurrentHashMap computeIfAbsent 嵌套时,ConcurrentHashMap的computeIfAbsent线程是否安全? - Is ConcurrentHashMap's computeIfAbsent thread safe, when nested? 为什么 ConcurrentHashMap.computeIfAbsent() 为已经存在的键增加计数器? - Why does ConcurrentHashMap.computeIfAbsent() increment the counter for an already present key? Java ConcurrentHashMap computeIfAbsent() 方法是否支持基于键的“锁定”? - Does Java ConcurrentHashMap computeIfAbsent() method support key-based “locking”? ConcurrentHashMap 的计算、computeIfAbsent 和computeIfPresent 方法是完全原子的吗? - Are ConcurrentHashMap's compute, computeIfAbsent and computeIfPresent methods entirely atomic? 为什么 ConcurrentHashMap::putIfAbsent 比 ConcurrentHashMap::computeIfAbsent 快? - Why ConcurrentHashMap::putIfAbsent is faster than ConcurrentHashMap::computeIfAbsent? 检查ConcurrentHashMap的computeIfAbsent是否更改了某些内容 - Check if computeIfAbsent of ConcurrentHashMap changed something ConcurrentHashMap computeIfAbsent 判断是否是第一次 - ConcurrentHashMap computeIfAbsent tell if first time or not computeIfAbsent 如何随机使 ConcurrentHashMap 失败? - How does computeIfAbsent fail ConcurrentHashMap randomly? ConcurrentHashMap.computeIfAbsent threadsafe中的赋值是什么? - Is an assignment inside ConcurrentHashMap.computeIfAbsent threadsafe?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM