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:
PENDING
? why not just use something like AtomicInteger
?setPendingCount()
, but sometimes it uses PENDING
, for example in addToPendingCount()
. And it even uses both, for example in decrementPendingCountUnlessZero()
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 insetPendingCount()
, but sometimes it usesPENDING
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.