preface
In today’s era of high-speed development of science and technology, the high-speed development of computer has already surpassed “Moore’s Law”. In this era of relatively cheap computers, the machine operated by developers is no longer a single-core processor, but a multi-core era, and business has long entered parallel execution. The key to developing highly concurrent programs is no longer to use inefficient locks, but fortunately JDK1.5 provides lock-free atomic operations in multi-threaded situations, which is what this article will cover.
What is “atomic variable class”?
Since JDK1.5 provides Java. Util. Concurrent. Atomic package, convenient programmers in a multithreaded environment, unlocked for atomic operations. The underlying atomic variable uses the atomic instructions provided by the processor, but different CPU architectures may provide different atomic instructions, and some form of internal locking may be required, so this method cannot absolutely guarantee that the thread is not blocked. – The general idea is to provide non-blocking thread-safe programming
Simple use of atomic variable classes
Understanding of Java JDK package before introduce usage. The util. Concurrent. Provides us with what atoms in the atomic classes and methods:
(1) Class abstract
class | describe |
---|---|
AtomicBoolean | Boolean values that can be updated atomically. |
AtomicInteger | An int value that can be updated atomically. |
AtomicIntegerArray | An int array whose elements can be updated atomically. |
AtomicIntegerFieldUpdater | Reflection – based utility that makes atomic updates to specified volatile int fields of specified classes. |
AtomicLong | A long value that can be updated atomically. |
AtomicLongArray | An array of Longs whose elements can be updated atomically. |
AtomicLongFieldUpdater | Reflection-based utility that makes atomic updates to specified volatile long fields of specified classes. |
AtomicMarkableReference | AtomicMarkableReference maintains object references with tag bits that can be updated atomically. |
AtomicReference | An object reference that can be updated atomically. |
AtomicReferenceArray | An array of object references whose elements can be updated atomically. |
AtomicReferenceFieldUpdater | Reflection – based utility that makes atomic updates to specified volatile fields of specified classes. |
AtomicStampedReference | AtomicStampedReference maintains an object reference with an integer “flag” that can be updated atomically. |
(2) Summary of common methods
The return type | methods | describe |
---|---|---|
boolean | compareAndSet(boolean expect, boolean update) | If the current value == expected value, the value is set atomically to the given updated value |
boolean | get() | Returns the current value. |
void | set(boolean newValue) | Unconditionally set to a given value. |
boolean | weakCompareAndSet(boolean expect, boolean update) | If the current value == expected value, the value is set atomically to the given updated value. |
This introduction lists only the common methods, which vary slightly from atomic class to atomic class. For more information, see “Online Documentation – JDK-Z”.
Online documentation – JDK -zh
(3) Simple usage examples
Example 1: Atomic update base type class – generate sequence numbers
public class Example1 {
private final AtomicLong sequenceNumber = new AtomicLong(0);
public long next(a) {
// the atomic increment method executes i++, so it needs to be fetched once.
sequenceNumber.getAndIncrement();
return sequenceNumber.get();
}
public void radixNext(int radix){
for (;;) {
long i = sequenceNumber.get();
// This method may not succeed, so an infinite loop is used to ensure that the operation will always succeed once.
boolean suc = sequenceNumber.compareAndSet(i, i + radix);
if (suc) {
break; }}}public static void main(String[] args) {
Example1 sequencer = new Example1();
// Generate the serial number
for (int i = 0; i < 10; i++) {
System.out.println(sequencer.next());
}
// Generate a custom sequence number
for (int i = 0; i < 10; i++) {
sequencer.radixNext(3); System.out.println(sequencer.sequenceNumber.get()); }}}Copy the code
Execution Result:
1
2
3
4
5
---------------
8
11
14
17
20Copy the code
Example 2: Update an array atomically
public class Example2 {
static AtomicIntegerArray arr = new AtomicIntegerArray(10);
public static class AddThread implements Runnable{
public void run(a){
for(int k=0; k<10000; k++){// Atomically increments the element in index I by 1.arr.getAndIncrement(k%arr.length()); }}}public static void main(String[] args) throws InterruptedException {
Thread[]ts=new Thread[10];
// Create 10 threads
for(int k=0; k<10; k++){ ts[k] =new Thread(new AddThread());
}
// Start 10 threads
for(int k=0; k<10; k++){ ts[k].start(); }// Wait for all threads to complete
for(int k=0; k<10; k++){ ts[k].join(); }// Prints the final execution resultSystem.out.println(arr); }}Copy the code
Execution Result:
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]Copy the code
Example 3: Atomic update references
public class Node {
private int val;
private volatile Node left, right;
private static final AtomicReferenceFieldUpdater leftUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "left");
private static AtomicReferenceFieldUpdater rightUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "right");
boolean compareAndSetLeft(Node expect, Node update) {
return leftUpdater.compareAndSet(this, expect, update);
}
public Node(a) {
this.left = this.right = null;
}
public Node(int val) {
this.val = val;
this.left = this.right = null;
}
public Node(Node left,Node right) {
this.left = left;
this.right = right;
}
public static void main(String[] args) {
Node node = new Node(1);
node.left = new Node(new Node(2),new Node(3));
node.right = new Node(new Node(4),new Node(5));
System.out.println(JSON.toJSON(node));
node.compareAndSetLeft(node.left,node.right);
System.out.println(JSON.toJSON(node));
}
// get and set ...
}Copy the code
Execution Result:
{"val": 1,"left": {"val": 0."left": {"val": 2}."right": {"val": 3}},"right": {"val": 0."left": {"val": 4},"right": {"val": 5}}} {"val": 1,"left": {"val": 0."left": {"val": 4},"right": {"val": 5}},"right": {"val": 0."left": {"val": 4},"right": {"val": 5}}}Copy the code
(4) Summary
The memory effects of atomic access and updates generally follow the declarations in the following mutable rules:
- Get has the memory effect of reading volatile variables.
- Set has the memory effect of writing (allocating) volatile variables. LazySet has the memory effect of writing (allocating) volatile variables, in addition to allowing subsequent (but not previous) memory operations and itself not imposing reordering constraints with ordinary non-volatile writes. In other usage contexts, when null (for garbage collection), lazySet can apply references that will not be accessed again.
- WeakCompareAndSet reads and conditionally writes variables atomically but does not create any happen-before ordering, and therefore does not provide any warranty regarding previous or subsequent read or write operations of any variable other than the -WeakCompareandset target.
- CompareAndSet and all other read and update operations (such as getAndIncrement) have the memory effect of reading and writing volatile variables.
The implementation principle of atomic operation
Key source code:
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();Copy the code
Looking at the source code, most of the classes in the Atomic package are implemented using Unsafe, which only provides the following three CAS methods.
Key source code:
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);Copy the code
It is not difficult to find that the method is modified by native, that is, when the method modified by native is called, it points to a specific implementation of non-Java code, which may be in other languages or operating systems. Here is the use of C to call CPU bottom instructions to achieve, the specific implementation principle please click the following “implementation principle”.
Realize the principle of
The purpose of an atomic object
Atomic variable classes are primarily used as building blocks for implementing non-blocking data structures and associated infrastructure classes. The compareAndSet method is not a normal lock replacement method. It is applied only if significant updates to the object are restricted to a single variable.
Example: Multithreading with high concurrency counters
Summary (from online)
Atomic variable classes have several performance advantages over lock-based versions. First, it replaces the JVM’s lock code path with the native form of the hardware, allowing synchronization at a finer level of granularity (independent memory locations), and failing threads can be retried immediately without being suspended and reschedulated. Finer granularity reduces the chance of contention, and the ability to retry without rescheduling reduces the cost of contention. Even with a small number of failed CAS operations, this approach is still much faster than rescheduling due to lock contention.