简体   繁体   English

ConcurrentHashMap 中的 put() 方法也是原子的吗?

[英]Is put() method in ConcurrentHashMap also atomic?

In ConcurrentHashMap , putIfAbsent() is atomic.ConcurrentHashMapputIfAbsent()是原子的。 My question is method put() in ConcurrentHashMap also atomic?我的问题是 ConcurrentHashMap 中的put()方法也是原子的吗?

It is not stated explicitly in the documentation that put or get are atomic.文档中没有明确说明putget是原子的。 However, the javadoc states this:但是, javadoc指出:

Retrievals reflect the results of the most recently completed update operations holding upon their onset.检索反映了最近完成的更新操作的结果。 (More formally, an update operation for a given key bears a happens-before relation with any (non-null) retrieval for that key reporting the updated value.) (更正式地,给定键的更新操作与报告更新值的该键的任何(非空)检索具有先发生关系。)

That implies if one thread does a put and another does a get with the same key, then the get will either see the "before the put" state or the "after the put" state.这意味着如果一个线程使用相同的键执行put而另一个线程执行get ,则get将看到“放置之前”状态或“放置后”状态。 That effectively means that get and put are atomic with respect to each other , and with respect to other explicitly atomic operations ... all for a given key.这实际上意味着, getput是原子相对于彼此,以及相对于其他明确的原子操作......所有给定的键。 Indeed, if this wasn't the case, then ConcurrentHashMap would not be thread-safe in the conventional / intuitive sense.事实上,如果不是这种情况,那么ConcurrentHashMap在传统/直观意义上就不是线程安全的。

However, the javadocs do not provide strong guarantees for operations involving different keys.但是,javadoc 没有为涉及不同键的操作提供强有力的保证。 Atomicity is therefore limited.因此原子性是有限的。

This kind of atomicity not a particularly interesting or useful property.这种原子性并不是一个特别有趣或有用的属性。 For example, while put and get are individually atomic, a put followed by a get is not atomic.例如,虽然putget分别是原子的,但put后跟get不是原子的。 It is hard to see how you would exploit the limited atomicity of these operations ... beyond general thread-safety.很难看出您将如何利用这些操作的有限原子性……超出一般线程安全性。

(IMO, that is probably the reason that they don't bother to explicitly mention the atomicity of get and put in the javadoc.) (IMO,这可能是他们懒得在 javadoc 中明确提及getput的原子性的原因。)

The more interesting property is the atomicity (or not) of the more complex operations.更有趣的特性是更复杂操作的原子性(或不是)。 For example, operations like putIfAbsent , compute and merge are atomic, but the bulk operations are not atomic.例如,像putIfAbsentcomputemerge这样的操作是原子的,但批量操作不是原子的。

If I was in charge of this API I'd add it to the docs, but there is an explanation:如果我负责这个 API,我会将它添加到文档中,但有一个解释:

put is inherently a singular operation. put本质上是一个单一的操作。 As in, the definition of what put does (from java.util.Map 's definition) is atomic.正如, put 所做的定义(来自java.util.Map的定义)是原子的。

Of course, just because some method task is described in an atomic fashion does not imply that the implementation is atomic, and for a great many things in the collections framework, they aren't.当然,仅仅因为一些方法任务以原子方式描述并不意味着实现是原子的,对于集合框架中的很多事情,它们不是。 ArrayList's add method is an atomic description, but not an atomic implementation. ArrayList 的 add 方法是原子描述,而不是原子实现。

The difference is, the description of the add job is simply 'add the element to the end'.不同之处在于,添加作业的描述只是“将元素添加到末尾”。 It is not 'search for the end node, and once found, add this element as tail reference to it'.它不是“搜索结束节点,一旦找到,就将此元素添加为它的尾部引用”。 It is not 'obtain the size of the list and remember it.它不是'获取列表的大小并记住它。 Then, check the capacity of this list, if it is insufficient to add items to it, expand the capacity by the growth factor first, then, set the value for the earlier obtained 'size' index to the passed in object.'然后,检查这个列表的容量,如果添加项不够,先按增长因子扩大容量,然后将之前获得的'size'索引的值设置为传入的对象。

Even though that latter long-winded description is more or less properly describing what ArrayList.add actually does.尽管后一种冗长的描述或多或少正确地描述了 ArrayList.add 的实际作用。

So, the javadoc of ConcurrentHashMap at the class level and at the method level work together:所以, ConcurrentHashMap在类级别和方法级别的 javadoc 一起工作:

put() is described inherently as an atomic operation, whilst putIfAbsent 's description is not atomic at all. put()本质上被描述为原子操作,而putIfAbsent描述根本不是原子的。 It's explicitly described as a two-step process;它被明确描述为一个两步过程; it's even in the name!它甚至在名字中! put, if absent - it's a two-step name.放置,如果不存在- 这是一个两步名称。 Therefore the docs need to go out of their way to say: Even though it sounds like a two-step operation, consider it atomic in purpose.因此,文档需要特意指出:尽管这听起来像是一个两步操作,但要考虑它是原子性的。

And then the class javadoc of CHM say that all operations intended to be atomic are in fact atomically implemented.然后 CHM 的 javadoc 类说所有打算成为原子的操作实际上都是原子实现的。

To give some contrast, something like putAll is described as a multi-step process, and the CHM docs do not explicitly state that putAll is atomic.为了提供一些对比,像putAll这样的东西被描述为一个多步骤过程,并且 CHM 文档没有明确说明putAll是原子的。 And indeed it isn't.事实并非如此。 You can observe half of the stuff you're adding with putAll having been added in a separate list (putAll acts pretty much exactly as if for (var e : in.entries()) put(e.getKey(), e.getValue()); is run.您可以观察到使用putAll添加的一半内容已添加到单独的列表中(putAll 的行为与for (var e : in.entries()) put(e.getKey(), e.getValue());几乎完全一样for (var e : in.entries()) put(e.getKey(), e.getValue());运行。

Oracle's documentation for ConcurrentHashMap::put​(K key, V value) does not explicitly state about the Atomicity of this method. Oracle 的ConcurrentHashMap::put​(K key, V value)文档没有明确说明此方法的原子性

Let us see this ourselves (OpenJDK 11 version "11.0.2" 2019-01-15 ):让我们自己看看(OpenJDK 11版本“11.0.2”2019-01-15 ):

V put(K key, V value) calls V putVal(K key, V value, boolean onlyIfAbsent) , which, in turn, looks like this: V put(K key, V value)调用V putVal(K key, V value, boolean onlyIfAbsent) ,它依次如下所示:

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (ConcurrentHashMap.Node<K,V>[] tab = table;;) {
        ConcurrentHashMap.Node<K,V> f; int n, i, fh; K fk; V fv;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new ConcurrentHashMap.Node<K,V>(hash, key, value)))
                break;                   // no lock when adding to empty bin
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else if (onlyIfAbsent // check first node without acquiring lock
                && fh == hash
                && ((fk = f.key) == key || (fk != null && key.equals(fk)))
                && (fv = f.val) != null)
            return fv;
        else {
            V oldVal = null;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (ConcurrentHashMap.Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                            (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            ConcurrentHashMap.Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new ConcurrentHashMap.Node<K,V>(hash, key, value);
                                break;
                            }
                        }
                    }
                    else if (f instanceof ConcurrentHashMap.TreeBin) {
                        ConcurrentHashMap.Node<K,V> p;
                        binCount = 2;
                        if ((p = ((ConcurrentHashMap.TreeBin<K,V>)f).putTreeVal(hash, key,
                                value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                    else if (f instanceof ConcurrentHashMap.ReservationNode)
                        throw new IllegalStateException("Recursive update");
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

As you can see, it's a partially atomic and thread-safe;如您所见,它是部分原子且线程安全的; however, it is not FULLY atomic operation, per se , and it depends on the state of the table, state of the bucket/entry you're adding, and some other peculiar details.然而,这并不完全原子操作本身,它依赖于表的状态,桶/进入您要添加,以及其他一些特殊细节的状态。

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

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