Search code examples
javaatomicjava-9compare-and-swap

What's the effect of "private static final VarHandle PENDING" in CountedCompleter class


I'm reading the source code of CountedCompleter in JDK9, here is the code related to my question:

public abstract class CountedCompleter<T> extends ForkJoinTask<T> {
    /** The number of pending tasks until completion */
    volatile int pending;

    // VarHandle mechanics
    private static final VarHandle PENDING;
    static {
        try {
            MethodHandles.Lookup l = MethodHandles.lookup();
            PENDING = l.findVarHandle(CountedCompleter.class, "pending", int.class);

        } catch (ReflectiveOperationException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * Sets the pending count to the given value.
     *
     * @param count the count
     */
    public final void setPendingCount(int count) {
        pending = count;
    }

    /**
     * Adds (atomically) the given value to the pending count.
     *
     * @param delta the value to add
     */
    public final void addToPendingCount(int delta) {
        PENDING.getAndAdd(this, delta);
    }

    /**
     * If the pending count is nonzero, (atomically) decrements it.
     *
     * @return the initial (undecremented) pending count holding on entry
     * to this method
     */
    public final int decrementPendingCountUnlessZero() {
        int c;
        do {} while ((c = pending) != 0 &&
                     !PENDING.weakCompareAndSet(this, c, c - 1));
        return c;
    }
}

Here are my questions:

  1. Why it uses pending and PENDING? why not just use something like AtomicInteger?
  2. Why sometimes it uses pending, for example in setPendingCount(), but sometimes it uses PENDING, for example in addToPendingCount(). And it even uses both, for example in decrementPendingCountUnlessZero()

Solution

  • Why it uses pending and PENDING?

    The former is the field, the latter is a handle onto it, to perform atomic operations upon the field.

    why not just use something like AtomicInteger?

    They could. I suspect it's just a performance thing. Most parts of the concurrency API are heavily optimized as they are highly likely to be involved in tight loops.

    AtomicInteger under the hood does basically the same things as are done here to assure atomic operations, so using it would be one level of indirection for method calls which is redundant with just a small amount of copy paste. Absolutely tiny performance gain, yes, but for some specialized applications that's going to be worth it.

    Yes, it would be cleaner code to re-use AtomicInteger, but the goals in the concurrency API are different than those of your or my projects. Performance is the #1 priority, and everything else is secondary.

    Why sometimes it uses pending, for example in setPendingCount(), but sometimes it uses PENDING

    It uses the volatile field directly for operations which are already atomic. setPendingCount just assigns to the field

    public final void setPendingCount(int count) {
        pending = count;
    }
    

    Other cases, where they need to compare-and-set need to function as atomic operations, and VarHandle provides that functionality

    public final boolean compareAndSetPendingCount(int expected, int count) {
        return PENDING.compareAndSet(this, expected, count);
    }
    

    And it even uses both, for example in decrementPendingCountUnlessZero()

    Again, the answer is atomicity. The way they have written it in this method is the most performant way to achieve atomicity for that function.