10.11 Using Atomic Types
Another advanced threading feature
introduced in Tiger is that of atomic type. An atomic operation is one that is indivisible: no other threads can
interrupt or examine a variable in the middle of an atomic operation.
There's the beginning state, the end state, and (for all other threads)
nothing in between. An atomic type is simply a type that has atomic
operations available to itit manages to be thread-safe despite being
essentially lock-free.
10.11.1 How do I do that?
All atomic types are defined in the java.util.concurrent.atomic package. There are a number of types, revolving around Boolean, Long,
Integer, and object references. This allows you to perform atomic operations on these types, using AtomicBoolean, AtomicLong, AtomicInteger, and AtomicReference, respectively.Each type provides a get( ) and set( ) method, which do what you
would expect (get and set the type's value, using an atomic operation).
They also offer getAndSet( ), which sets the value, returning the previous
value, as well as compareAndSet( ), which checks the value, and if it
matches the supplied value, sets it to a new value. Additionally,
AtomicInteger and AtomicLong provide for atomic versions of ++ and --,
through variations on decrement( ) and increment( ) methods. For example,
decrementAndGet( ) decrements the value of the atomic type, and
returns the update value; getAndIncrement( ) returns the current value,
and then increments it in the type. Here are several different ways to
write a thread-safe counter, lifted straight out of Java in a Nutshell, Fifth Edition (O'Reilly):NOTEThere are some variations on these types, with additional features, that you can check out
in the Tiger Javadocs.
If you're not familiar with object references, it's simply the reference to an
// Rely on locking to prevent concurrent access
int count1 = 0;
public synchronized int count1( ) {
return count1++;
}
// Rely on the atomic operations to prevent concurrent access
AtomicInteger count2 = new AtomicInteger(0);
public int count2( ) {
return count2.getAndIncrement( );
}
// Optimistic locking -- compare the result, to minimize overhead,
// and only correct if needed
AtomicInteger count3 = new AtomicInteger(0);
public int count3( ) {
int result;
do {
result = count3.get( );
} while (!count3.compareAndSet(result, result+1));
return result;
}
object. AtomicReference, then, allows you to work with an object atomically,
by getting and setting the reference in an indivisible manner. The
most useful method on AtomicReference is probably compareAndSet( ), which lets you change an object reference if it doesn't match the supplied
value.NOTE"compareAndSet" is the canonical atomic operation.Like the lab "Advanced Synchronizing," getting too much further into
atomic types would have us well into the ground covered by Java
Threads (O'Reilly), so I'll refer you to that work if you need to get further
detail on atomic types.
10.11.2 What about...
...types like byte, short, and char? These (and their wrapper types) can
all be stored in an AtomicInteger, providing you the same functionality.
You'll just have to do a little conversion on the object's return values,
which are almost always an int.You can also use these atomic operations on arrays of the Integer,
Long, and reference types (but not Boolean). java.util.concurrent.atomic defines AtomicIntegerArray, AtomicLongArray, and AtomicReferenceArray for just these occasions. They provide all the methods of their non-array counterparts, but each method takes an
int index to indicate which item in the array you want to operate
upon.