With the release of Concurrency API Java added a new java.util.concurrent.atomic package to help coordinate access to primitive values and object references. As with most classes in the Concurrency API, these classes are added solely for convenience.
For instance, the increment operator ++ is not thread-safe. The reason that it is not thread-safe is that the operation is not atomic, carrying out two tasks, read and write, that can be interrupted by other threads. That leads to so called lost update. Lost update occurs when one thread overwrites changes made by another thread.
Atomic is the property of an operation to be carried out as a single unit of execution without any interference by another thread. A thread-safe atomic version of the increment operator would be one that performed the read and write of the variable as a single operation, not allowing any other threads to access the variable during the operation.
Using atomics does not imply blocking access to data on JVM level (as with synchronized or locks). Atomics exploit low-level atomic machine instructions, such as compare-and-swap (CAS). CAS works in a loop mode. Typical CAS operation works on three operands :
- The memory location on which to operate (M)
- The existing expected value (A) of the variable
- The new value (B) which needs to be set
The CAS operation updates atomically the value in M to B, but only if the existing value in M matches A, otherwise no action is taken. Each thread according to the algorithm above does the following :
- Creates a local copy of the shared data
- Modifies the local copy as needed
- When ready, updates the shared data by swapping it with the local copy created before
Point 3. is the key : the swap is performed atomically through an atomic operation. That is why the algorithm above is called lock-free algorithm – in lock-free algorithm threads get in touch only during that tiny atomic swap, running undisturbed and unaware of others for the rest of the time. If swapping has failed (local copy value of the shared data did not match the value in shared data, i. e. the value in shared data was changed by another thread), then algorithm repeats, and repeats until swap is successful. It is a spin until success strategy, also called spin lock.
The Concurrency API includes useful classes that are conceptually the same as primitive classes but that support atomic operations. Using compare-and-swap (CAS) is tightely coupled with data it operates on, so each of atomic classes is tied with the type of data :
Class Name | Description |
AtomicBoolean | A boolean value that may be updated atomically |
AtomicInteger | An int value that may be updated atomically |
AtomicIntegerArray | An int array in which elements may be updated atomically |
AtomicLong | A long value that may be updated atomically |
AtomicLongArray | A long array in which elements may be updated atomically |
AtomicReference | A generic object reference that may be updated atomically |
AtomicReferenceArray | An array of generic object references in which elements may be updated atomically |
Each of the classes above includes numerous methods that are equivalent to many of the primitive built-in operators that are used on primitives, such as the assignment operator = and the increment operator ++. See table below :
Method Name | Description |
get() | Retrieve the current value |
set() | Set the given value, equivalent to the assignment = operator |
getAndSet() | Atomically sets the new value and returns the old value |
incrementAndGet() | For numeric classes, atomic pre-increment operation equivalent to ++value |
getAndIncrement() | For numeric classes, atomic post-increment operation equivalent to value++ |
decrementAndGet() | For numeric classes, atomic pre-decrement operation equivalent to –value |
getAndDecrement() | For numeric classes, atomic post-decrement operation equivalent to value– |