Java并发HashMap initTable()为什么使用try/finally块?

huangapple go评论72阅读模式
英文:

Java Concurrent Hashmap initTable() Why the try/finally block?

问题

以下是翻译的代码部分:

/**
 * 初始化表格,使用在 sizeCtl 中记录的大小。
 */
private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
        if ((sc = sizeCtl) < 0)
            Thread.yield(); // 失去初始化竞争;只需自旋
        else if (U.compareAndSetInt(this, SIZECTL, sc, -1)) {
            try {
                if ((tab = table) == null || tab.length == 0) {
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    sc = n - (n >>> 2);
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

如果需要进一步解释,请提问。

英文:

I have been looking at the following code (sourced from here)'

/**
 * Initializes table, using the size recorded in sizeCtl.
 */
private final Node&lt;K,V&gt;[] initTable() {
    Node&lt;K,V&gt;[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
        if ((sc = sizeCtl) &lt; 0)
            Thread.yield(); // lost initialization race; just spin
        else if (U.compareAndSetInt(this, SIZECTL, sc, -1)) {
            try {
                if ((tab = table) == null || tab.length == 0) {
                    int n = (sc &gt; 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings(&quot;unchecked&quot;)
                    Node&lt;K,V&gt;[] nt = (Node&lt;K,V&gt;[])new Node&lt;?,?&gt;[n];
                    table = tab = nt;
                    sc = n - (n &gt;&gt;&gt; 2);
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

Can someone explain why there is a need for the try block?

答案1

得分: 5

未经检查的异常和错误存在于一个概念中。如果在ConcurrentHashMap上调用.put(),并且内存非常紧张,那么该映射可能会尝试创建一个数组,而该调用可能会因为OutOfMemoryError而失败。代码仍将继续执行(可能是在捕获此异常的catch块中),并且对该映射的引用仍然存在。如果在此之后发生这种情况,映射崩溃并且完全无效,因为sizeCtl具有损坏的值,将会是一件很糟糕的事情。

关键是最终块,它恢复了sizeCtl的值。这用于各种事情,特别包括管理哪个线程可以访问。如果没有它,任何其他的put调用将会一直旋转。

这的相关性(即,在这里有多少次可抛出的异常,与移除'try'和'finally',并以sizeCtl = sc;结束,而不增加try/finally的额外开销相比)很低,但如果相关,它就非常相关。

英文:

unchecked exceptions and errors exist as a concept. If you call .put() on a ConcurrentHashMap, and you're real tight on memory, that map may try to make an array, and that call may fail with an OutOfMemoryError. Code will still continue (at the catch block that catches this, perhaps), and the references to that map still exist. It would be a bit shite if, after that happens, the map crashes and is completely invalidated because sizeCtl has a broken value.

The point is that finally block, which restores the sizeCtl value. This is used for various things, notable including managing which thread gets access. If it didn't, any other put calls would spin forever.

The relevance of this (as in, how often does a throwable occur here, vs. removing the 'try' and the 'finally' and just ending with sizeCtl = sc; without the additional overhead of try/finally) is low, but if it is relevant, it is quite relevant.

答案2

得分: 1

这是一种互斥锁 - 以及双重检查锁定。

首先,

if ((sc = sizeCtl) < 0)
    Thread.yield(); // 失去初始化竞争;只需自旋

检查 sizeCtl 互斥锁。如果它小于零,说明其他线程已经获取了它,因此我们只需等待一段时间。

如果它是零(或更多),我们有机会获得锁。因此执行了一个比较并交换的操作:

if (U.compareAndSetInt(this, SIZECTL, sc, -1)

这是一个原子操作 - 如果另一个线程在第一次和第二次检查之间获取了锁,那么 if 就不会执行。

如果CAS操作成功,那么该方法就负责释放互斥锁。因此,使用 try-finally 来确保无论是否发生异常(例如,在为新节点分配内存时出现内存不足的情况,或者如果地图适用于唯一限制),都会释放互斥锁(sizeCtl 重置为其原始值)。

英文:

It's a kind of mutex - and a double-check lock.

Firstly

if ((sc = sizeCtl) &lt; 0)
    Thread.yield(); // lost initialization race; just spin

checks the sizeCtl mutex. If it's less than zero, someone else has grabbed it, so we just wait for a bit.

If it's zero (or more), chances are we'd obtain the lock. So a Compare-And-Swap is performed:

if (U.compareAndSetInt(this, SIZECTL, sc, -1)

This is an atomic operation - if another thread grabbed it between the first and second check, the if would not be taken.

If the CAS is successful, then the method becomes reponsible for releasing the mutex. Therefore, a try-finally is used to ensure that the mutex is released (sizeCtl reset to its original value) irrespective of whether an exception occurs (e.g. out of memory assigning the new Node - or if there is a unique restriction applicable to the map) or not.

huangapple
  • 本文由 发表于 2020年8月30日 04:25:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/63651473.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定