1. Atomic classes under J.U.C
1.1 Introduction to atomic classes
- Atomic class, the data operation of the object is indivisible. atomicity
- Functions: Atomic classes are similar to locks, in order to ensure the safety of threads in the case of concurrency, but atomic classes have certain advantages compared to locks:
- Finer granularity: Atomic classes narrow the field of contention down to the variable level
- More efficient: More efficient in general, but less efficient in highly competitive situations
- Atomic classes under J.U.C, mostly implemented by CAS (source code will be analyzed at the end)
1.2 Atomic*
Base type atomic class
- AtomicInteger, for example
- Commonly used method
int get()
Get the current valueint getAndSet(int)
Gets the current value and sets the new valueint getAndIncrement()
Gets the current value and incrementsint incrementAndGet()
Get the increment value (compared to the same method, get gets the increment value before, get gets the increment value after)int getAndDecrement()
Gets the current value and decrementint getAndAdd(int)
Gets the current value and adds a valueboolean compareAndSet(int expect, int update)
Determine whether the current value matches the expected valueexpect
If yes, set the updated valueupdate
.
- Use the sample
/** * use AtomicInteger to compare non-atomic classes to illustrate thread-safety issues **@author yiren
*/
public class AtomicIntegerExample01 {
private static AtomicInteger atomicInteger = new AtomicInteger();
private static volatile Integer count = 0;
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
for (int i = 0; i < 10000; i++) { atomicInteger.getAndIncrement(); count++; }}; Thread thread1 =new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("atomicInteger=" + atomicInteger);
System.out.println("count="+ count); }}Copy the code
atomicInteger=20000
count=13409
Process finished with exit code 0
Copy the code
-
We can see that the AtomicInteger result is correct at this point
-
Integer, on the other hand, is incorrect and locks are required if thread safety is to be maintained
-
A single method atomic class is thread-safe as long as it is not called multiple times in the thread
-
Note: Atomic manipulation + atomic manipulation! = atomic operation
1.3 Atomic*Array
Array type analysis
-
AtomicIntegerArray, for example
-
Array type, which guarantees thread-safe operations on each element
-
The AtomicIntegerArray method is similar to the AtomicInteger method, except that the AtomicIntegerArray method needs to specify the index of the array
-
Code demo:
/ * * *@author yiren
*/
public class AtomicIntegerArrayExample {
private static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
public static void main(String[] args) throws InterruptedException {
Runnable incrRunnable = () -> {
for (int i = 0; i < atomicIntegerArray.length(); i++) { atomicIntegerArray.incrementAndGet(i); }}; Runnable decrRunnable = () -> {for (int i = 0; i < atomicIntegerArray.length(); i++) { atomicIntegerArray.decrementAndGet(i); }}; ExecutorService executorService = Executors.newFixedThreadPool(100);
for (int i = 0; i < 1000; i++) {
executorService.execute(incrRunnable);
}
for (int i = 0; i < 1000; i++) {
executorService.execute(decrRunnable);
}
TimeUnit.SECONDS.sleep(5);
for (int i = 0; i < atomicIntegerArray.length(); i++) {
System.out.print(atomicIntegerArray.get(i) + ""); }}}Copy the code
0 0 0 0 0 0 0 0 0 0
Copy the code
1.4 AtomicReference
Application Type Analysis
AtomicReference
The role of classes, andAtomicInteger
It’s not much different, it’s just the object is different,AtomicInteger
Is to guarantee the atomicity of an integer, andAtomicReference
Is to make an object atomic- while
AtomicReference
thanAtomicInteger
More powerful, because objects contain many properties that are used similarly - Class object method
- case
/ * * *@author yiren
*/
public class SpinLock {
private static AtomicReference<Thread> sign = new AtomicReference<>();
private static void lock(a) {
Thread current = Thread.currentThread();
while(! sign.compareAndSet(null, current)) {
System.out.println("fail to set!"); }}private static void unlock(a) {
Thread thread = Thread.currentThread();
sign.compareAndSet(thread, null);
}
public static void main(String[] args) {
Runnable runnable = () -> {
System.out.println("start to get lock");
SpinLock.lock();
System.out.println("got lock successfully!");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{ SpinLock.unlock(); }}; Thread thread =new Thread(runnable);
Thread thread1 = newThread(runnable); thread.start(); thread1.start(); }}Copy the code
- We use
AtomicReference
To implement a spin lock, throughcompareAndSet
Method to compare and then assign to avoid using locks
1.5 Encapsulate common types into atomic classes
-
AtomicIntegerFieldUpdater, for example
-
AtomicIntegerFieldUpdater.newUpdater(Counter.class, “count”); When we create an object, we specify the target class and the properties.
-
And when you operate, you need to pass in the object of the operation
-
Why is it necessary to do this? Instead of directly modifying the original object?
- If we use atomic operations only very rarely in our coding, it would be a waste of performance to use atomic classes directly on native objects
- In addition, when we use a class defined by others, we have such requirements, but others do not have such requirements, we can not break their definition, when
AtomicIntegerFieldUpdater
Without intrusive destruction of the original class.
-
Note: This class does not support static modified variables
-
Examples are as follows:
/ * * *@author yiren
*/
public class AtomicFieldUpdaterExample {
private static Counter one = new Counter();
private static Counter two = new Counter();
private static AtomicIntegerFieldUpdater<Counter> updater = AtomicIntegerFieldUpdater.newUpdater(Counter.class, "count");
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
for (int i = 0; i < 10000; i++) { one.count++; updater.getAndIncrement(two); }}; Thread thread1 =new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("one.count = " + one.count);
System.out.println("two.count = " + two.count);
}
private static class Counter {
volatile intcount; }}Copy the code
one.count = 18417
two.count = 20000
Process finished with exit code 0
Copy the code
- As you can see, after the upgrade, the original data manipulation thread is safe
1.6 Adder
accumulator
-
LongAdder, for example
-
LongAdder is a new class introduced in Java8. LongAdder is more efficient than AtomicLong at high concurrency because it uses space for time
-
In fact, it makes use of segment-based lock technology. LongAdder will modify different threads on different cells to reduce the probability of conflicts and improve the concurrency performance.
- Code demo: comparison
AtomicLong
andLongAdder
/ * * *@author yiren
*/
public class AtomicLongExample {
public static void main(String[] args) {
AtomicLong counter = new AtomicLong();
ExecutorService executorService = Executors.newFixedThreadPool(16);
Runnable task = () -> {
for (int i = 0; i < 10000; i++) { counter.incrementAndGet(); }};long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
executorService.execute(task);
}
executorService.shutdown();
while(! executorService.isTerminated()) { }long end = System.currentTimeMillis();
System.out.println("end-start=" + (end - start)+ "ms"); }}Copy the code
end-start=2140ms
Process finished with exit code 0
Copy the code
/ * * *@author yiren
*/
public class LongAdderExample {
public static void main(String[] args) {
LongAdder counter = new LongAdder();
ExecutorService executorService = Executors.newFixedThreadPool(16);
Runnable task = () -> {
for (int i = 0; i < 10000; i++) { counter.increment(); }};long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
executorService.execute(task);
}
executorService.shutdown();
while(! executorService.isTerminated()) { }long end = System.currentTimeMillis();
System.out.println("end-start=" + (end - start)+ "ms"); }}Copy the code
end-start=157ms
Process finished with exit code 0
Copy the code
- As you can see from the above, my local machine is an I7 processor, and the only difference between the two programs is that the atomic classes used are more than 10 times different.
LongAdder
Obviously thanAtomicLong
faster
- Why the big difference?
AtomicLong
First, after each thread completes the operation, it needs to flush the data from the thread’s local memory to the main memory, and then another thread has to flush the new data from the main memory.- Note: You need to know about JMM: the principles and applications of the JMM (Java Memory Model) in concurrency
- while
LongAdder
You don’t have to do that,LongAdder
Each thread has its own counter that counts only within its own thread so that it does not interfere with other threads’ counters. LongAdder
I’m going to introduce the idea of piecewise accumulation, and I have one insidebase
Variables and oneCell[] cells
Arrays participate in the calculationbase
: Directly adds to this variable if the competition is not fiercecells
: When the competition is intense, each thread will scatter and add to its owncells[i]
In the
- Applicable scenario
AtomicLong
: Sum in case of low competitionLongAdder
Similar, but with CAS methods, it provides more functionalityLongAdder
: has obvious advantages in the case of high concurrency, but only applies to the scene of statistics and counting, has certain limitations
1.7 Accumulator
accumulator
-
LongAccumulator, for example
-
Basic usage
/** * @author yiren */ public class AccumulatorExample {public static void main(String[] args) { Long::sum left=3 LongAccumulator LongAccumulator = new LongAccumulator((left, right) right) -> left + right, 3); // left=3+right=3+2=5 longAccumulator.accumulate(2); // left=5+right=5+3=8 longAccumulator.accumulate(3); System.out.println(longAccumulator.getThenReset()); // left=3 LongAccumulator longAccumulator1 = new LongAccumulator((left, right) -> left - right, 3); // left=3-right=3-2=1 longAccumulator1.accumulate(2); // left=1-right=1-3=-2 longAccumulator1.accumulate(3); System.out.println(longAccumulator1.getThenReset()); LongAccumulator Longator2 = new LongAccumulator(Math:: Max, -1); longAccumulator2.accumulate(14); longAccumulator2.accumulate(3); System.out.println(longAccumulator2.getThenReset()); }}Copy the code
8
-2
14
Process finished with exit code 0
Copy the code
- See the notes for details
- Some might find this troublesome. This is just single-threaded, atomic classes are guaranteed to be multithreaded. This means that we can call directly from different threads
/ * * *@author yiren
*/
public class AccumulatorExample01 {
public static void main(String[] args) {
LongAccumulator accumulator = new LongAccumulator((left, right) -> {
long y = left;
long x = right;
return x + y;
}, 0);
ExecutorService executorService = Executors.newFixedThreadPool(100);
IntStream.range(1.100).forEach(item -> executorService.execute(() -> accumulator.accumulate(item)));
executorService.shutdown();
while (!executorService.isTerminated()) {
}
System.out.println(accumulator.get());
}
}
Copy the code
4950
Process finished with exit code 0
Copy the code
- Usage Scenarios:
- It requires parallel computation, large amount of data
- There is no order required
2. The principle of CAS
2.1 What is CAS?
-
CAS stands for Compare and Swap
-
CAS has three values. The memory Value Value, the expected Value Expect, the Value to be modified Target, and the memory Value can be changed to Target only if and only if Expect==Value, otherwise nothing is done. Finally, the current Value is returned
-
In modern processors, CAS is implemented with special instructions, and JVM implementations also use assembly instructions: CMPXCHG
2.2 Case Demonstration
- CAS equivalent code:
/**
* @author yiren
*/
public class CasExample {
private static volatile int value;
public static synchronized int compareAndSwap(int expect, int target) {
int oldValue = value;
if (expect == oldValue) {
value = target;
}
returnvalue; } public static void main(String[] args) throws InterruptedException { value = 0; Runnable runnable = () -> { compareAndSwap(0, 1); }; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(value); }}Copy the code
2.3 Source code analysis CAS
-
Take the source code of the atomic class AtomicInteger as an example
-
Here is the attribute definition in AtomicInteger:
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw newError(ex); }}private volatile int value;
Copy the code
-
Where value is the attribute where we mainly save data; ValueOffset, on the other hand, represents the offset address of the current object in the memory address for the value of the variable. Therefore, unaddressed is the address for retrieving the original value of data, so we can implement CAS using Unsafe
-
Let’s look at AtomicInteger in action
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final int getAndDecrement(a) {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
Copy the code
-
The unsafe methods are compareAndSwapInt, getAndAddInt, and the unsafe method calls each pass in the current object, the offset address of value, and the operand
-
Unsafe class: The Unsafe class is the core of the CAS implementation. Java does not have direct access to the underlying operating system, but rather through native methods. However, the JVM does provide a way to access the Unsafe class in the JDK, which provides hardware-level atomic operations.
-
Let’s look at the int getAndAddInt(Object var1, Long Var2, int var4) 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
- Var1 is the current AtomicInteger object, var2 is the offset address of value, used by the offset address
Unsafe
thegetIntVolatile
Retrieves the value of the current AtomicInteger object and callsUnsafe
thecompareAndSwapInt
Method to do CAS. - Look at the
compareAndSwapInt
The definition of
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
Copy the code
- It is a native method that actually calls the C++ implementation of the JVM, which in C++ code calls
Atomic::cmpxchg
In modern processors, it is actually possible to compare and exchange instructions corresponding to the assembly instruction setCMPXCHG
2.5 Disadvantages of CAS
- ABA problem
Such as
- The initial value is 0, thread 1 changes it to 1, and then changes it back to 0
- Thread 2 gets a 0 before thread 1 changes to 1, and then compares it after thread 1 changes back to 0.
- Thread 2 is successful, but thread 2 doesn’t know that thread 1 changed the number inside
- This can be solved by using version numbers such as 1A->2B-> 3a-4b, where each operation has a version number as a record. The version number is used to compare
- There is an Atom Stampedreference in Java for solving ABA problems
- In the case of high concurrency, it is inefficient and may require many comparisons
- Article content source:
- Java concurrent programming art, JDK1.8 version of the source code, MOOC wukong JUC course
- Think you can point a thumbs-up 👍 Thanks!
About me
- Majoring in Computer Science and technology, general university, Hangzhou.
- Graduated in 20 years, mainly engaged in back-end development of Java technology stack.
- GitHub: github.com/imyiren
- Blog : imyi.ren