Atomic Variables

Unless the action on a shared volatile variable is atomic, thread-safety cannot be guaranteed because of potential race conditions. Non-atomic actions (such as the use of the increment operator) by different threads can result in interleaving of constituent actions like read, update, and write.

Shared lock-free thread-safe variables are implemented by the classes in the java.util.concurrent.atomic package (Table 23.4). The classes provide methods that implement atomic actions. The classes and the variables that denote their instances are called atomic classes and atomic variables, respectively.

Atomic variables also behave as if they are volatile, so there is no need to declare them explicitly with the volatile keyword—that is, the atomic actions of the atomic classes guarantee visibility of an atomic variable to other threads.

The atomic classes thus implement non-blocking lock-free synchronization of atomic variables by a predefined set of atomic actions, ensuring visibility and avoiding race conditions.

Table 23.4 Selected Atomic Classes

Atomic classes in the java.util.concurrent.atomic packageMutable value that may be updated atomically
AtomicBooleanA boolean value
AtomicInteger implements NumberAn int value
AtomicLong implements NumberA long value
AtomicReference<V>An object reference

Example 23.11 illustrates a thread-safe counter implemented using the Atomic-Integer class. The counter field is declared at (1) as an AtomicInteger and initialized to 0. The getValue() and increment() methods at (2) and (3) delegate their operation to appropriate atomic methods of the AtomicInteger class. As the name implies, the get() method atomically returns the current value of the counter. The increment-AndGet() method atomically increments the counter and returns the new value (that is ignored in this example). The incrementAndGet() method entails more than one action, but none of them can be interrupted. The output shows that the counter was correctly incremented by the threads.

To understand how some of the atomic operations are implemented, we take a look at the while loop at (5), which implements the equivalent of the incrementAndGet() method at (4).

The strategy used in the while loop is known as CAS: Compare and Set, after the method called at (8). The compareAndSet() method will not set the counter to the new value unless the current value is equal to the expected value.

Assume that the current value in the counter is 100. Referring to the numbered lines in the code:

(6) The expected value is 100 (i.e., the current counter value).

(7) The new value is 101, after incrementing the expected value.

(8) The call to the compareAndSet() method in the conditional expression of the if statement returns true, since the current value (100) is equal to the expected value (100). The new value (101) is set in the counter—that is, it is correctly incremented by 1 from the previous value.

(9) The loop terminates.

Assume that by the time the current value is read again for comparison at (8) by the compareAndSet() method, it has been changed to 105 by another thread. This is possible by another thread in the time between the counter value is accessed at (6) by the get() method and then again at (8) by the compareAndSet() method—that is, there is a potential race condition.

(8) The call to the compareAndSet() method in the conditional expression of the if statement returns false, since the current value (105) is not equal to the expected value (100). The value in the counter is not changed as it could corrupt the state of the counter, and the loop continues until the counter value is correctly incremented by 1.