Earlier we introduced two keywords in Concurrent programming in Java: volatile and synchronized. We’ve also learned that volatile, while lightweight, does not guarantee atomicity, and synchronized does guarantee atomicity but is heavier.

So is there a simple, high-performance way to guarantee atomic operations in Java? This article takes a look at some of the Atomic packages that have been added to the Java family since JDK1.5. The Atomic package contains 12 classes divided into four types:

  • Atomic update base type

  • Atomic update array

  • Atomic update reference

  • Atomic update field

Let me introduce you one by one.

Basic atomic type

Atomic primitive types, as the name suggests, are classes that provide atomic operations for primitive types. They are the following 3 bits:

  • AtomicBoolean

  • AtomicInteger

  • AtomicLong

These three are close relatives and provide basically identical methods (AtomicBoolean supports slightly fewer methods).

Here we introduce these methods using AtomicInteger as an example.

  • Void lazySet(int newValue) : This method will eventually be set to newValue. Is thread unsafe. Here’s the official explanation:

    As probably the last little JSR166 follow-up for Mustang, we added a “lazySet” method to the Atomic classes (AtomicInteger, AtomicReference, etc). This is a niche method that is sometimes useful when fine-tuning code using non-blocking data structures. The semantics are that the write is guaranteed not to be re-ordered with any previous write, but may be reordered with subsequent operations (or equivalently, might not be visible to other threads) until some other volatile write or synchronizing action occurs).

    The main use case is for nulling out fields of nodes in non-blocking data structures solely for the sake of avoiding long-term garbage retention; it applies when it is harmless if other threads see non-null values for a while, but you’d like to ensure that structures are eventually GCable. In such cases, you can get better performance by avoiding the costs of the null volatile-write. There are a few other use cases along these lines for non-reference-based atomics as well, so the method is supported across all of the AtomicX classes.

    For people who like to think of these operations in terms of machine-level barriers on common multiprocessors, lazySet provides a preceeding store-store barrier (which is either a no-op or very cheap on current platforms), but no store-load barrier (which is usually the expensive part of a volatile-write).

    It is explained here that this method cannot reorder with previous writes; it can reorder with subsequent writes until volatile writes or synchronizing operations occur. The benefit is better performance than the normal set method, provided that you can tolerate other threads reading old data for a while.

  • Int getAndSet(int newValue) : Updates atomically and returns the old value.

  • Boolean compareAndSet(int expect, int update) : Updates atomically if the input value is equal to expect’s value.

  • Int getAndIncrement() : returns the value before increment.

  • Int getAndDecrement() : In contrast to getAndIncrement, the values before the reduction are returned.

  • Int getAndAdd(int delta) : Atomically adds the current value to the input value, returning the value before the calculation.

  • Int incrementAndGet() : increments atomically and returns the incremented value.

  • Int decrementAndGet() : decrements atomically, returning the decremented value.

  • Int addAndGet(int delta) : Atomically adds the current value to the input value and returns the calculated value.

  • Int getAndUpdate (IntUnaryOperator updateFunction) : Java1.8 adds a method that atomically updates the current value as specified and returns the value before it was updated. Note that the provided method should be side-lee-free, i.e. the same result twice, because it will try to execute the method again if the update fails due to thread contention.

  • Int updateAndGet(IntUnaryOperator updateFunction) : returns the updated value.

  • Int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) : Similar to the above two methods, the operand is supplied by the parameter x. Returns the value before the update.

  • Int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) : This is the same as getAndAccumulate.

How does AtomicInteger implement atomic operations? GetAndIncrement method getAndIncrement method

/** * Atomically increments by one the current value. * * @return the previous value */public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }Copy the code

Moving on to the Unsafe method, the getAndAddInt method

public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(! this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }Copy the code

GetIntVolatile is a local method that fetches values based on objects and offsets. It then executes the compareAndSwapInt method, which gets the current value based on the object and offset, compares it to the desired value, VAR5, and updates the value to var5+ VAR4 if it is equal. Otherwise, enter the loop. If you want to know the UnSafe of other methods, can read the source code or reference/UnSafe in the Java class, this article (https://www.cnblogs.com/mickole/articles/3757278.html).

An array of atoms

The following classes are classes that provide atomic operations for updating an element in an array.

  • AtomicIntegerArray

  • AtomicLongArray

  • AtomicReferenceArray

The methods in all three classes are similar:

Let’s introduce the methods in AtomicIntegerArray.

  • AtomicIntegerArray(int Length) : constructor to create an array, passing in the AtomicIntegerArray.

  • AtomicIntegerArray(int[] array) : constructor to clone an array and pass in an AtomicIntegerArray. Therefore, modifying the elements in the AtomicIntegerArray does not affect the original array.

  • Int length() : Gets the length of the array.

  • Int get(int I) : Gets the element at position I.

  • Void set(int I, int newValue) : sets the value of the corresponding position.

  • Void lazySet(int I, int newValue) : similar to lazySet in AtomicInteger.

  • Int getAndSet(int I, int newValue) : updates the value of the corresponding position, returns the value before the update.

  • Boolean compareAndSet(int I, int expect, int update) : compareAndSet(int I, int expect, int update) : compareAndSet(int I, int expect, int update) : compareAndSet(int I, int expect, int update) If not, return false.

  • Int getAndIncrement(int I) : returns the value before the update when the element at position I is incremented.

  • Int getAndDecrement(int I) : Atomically decreases elements in position I, returning the values before the update.

  • Int getAndAdd(int I, int delta) : Evaluates the element at position I atomically and returns the value before the update.

  • Int incrementAndGet(int I), int decrementAndGet(int I), addAndGet(int I, int delta) : These methods operate the same as the other three, except that they return updated values.

The following four methods, all added in 1.8, operate on the element at position I based on the methods in the supplied arguments. The difference is the return value and whether operands are provided.

  • int getAndUpdate(int i, IntUnaryOperator updateFunction)

  • int updateAndGet(int i, IntUnaryOperator updateFunction)

  • int getAndAccumulate(int i, int x, IntBinaryOperator accumulatorFunction)

  • int accumulateAndGet(int i, int x, IntBinaryOperator accumulatorFunction)

The atomic array type is also a method for calling the Unsafe class, so it works the same way as the basic type, which isn’t covered here.

Atomic reference type

All the previous types can only update one variable atomically. Is there a way to update multiple variables atomically? We can take advantage of the idea of object-oriented encapsulation, which can encapsulate multiple variables into a class, and then update a class object atomically. Fortunately, Atomic provides us with a way to update the reference type. Let’s meet them.

  • AtomicReference

  • AtomicReferenceFieldUpdater

  • AtomicMarkableReference

Again, let’s take a look at what methods these three classes provide.

The function of the method is similar to that in AtomicInteger, which I won’t go into too much detail.

/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.* @param expect the expected value* @param update the new value* @return {@code true} if successful. False return indicates that*  the actual value was not equal to the expected value.*/public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); }Copy the code

This is the source for the compareAndSet method, which also calls the CAS method of the UnSafe class, so atomic operations work the same way as the basic type.

Atomic update field class

Mentioned above the AtomicReferenceFieldUpdater class, it updates the class field, in addition to the class, Atomic also provides three other classes are used to update the class of the field:

  • AtomicIntegerFieldUpdater

  • AtomicLongFieldUpdater

  • AtomicStampedReference

When using these classes, note the following:

  1. The update field must be decorated with the volatile keyword

  2. Update fields cannot be class variables

  3. The newUpdater() method is called to create a Updater before use

The method semantics of these three classes are also very clear, see AtomicInteger.

conclusion

The Atomic package provides enough Atomic classes to work with, but it will take a lot of practice to really understand them fully. To learn more about concurrent programming in Java, try quietly replying “Concurrent programming in Java” in the background.

If you think the article is good, help to like or forward, thank you ~