简体   繁体   中英

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

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

/**
 * Initializes table, using the size recorded in 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(); // lost initialization race; just spin
        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;
}

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

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.

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

Firstly

if ((sc = sizeCtl) < 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 (eg out of memory assigning the new Node - or if there is a unique restriction applicable to the map) or not.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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