What is atomicity?
Atomicity means that an operation or operations will either succeed or fail, with no partial success or partial failure due to interruption.
Is i++ thread-safe?
Today we will start with this question to explain atomic operations in JAVA.
The i++ operation was known when we learned the basics of JAVA, but did you know it was thread-safe?
There must be someone in your heart, HMM… No, there is an answer!
Let’s do an experiment:
@Slf4j
public class Add {
public static void main(String[] args) {
AddThread addThread = new AddThread();
// Create 10 threads
IntStream.range(0.10).forEach(
value -> new Thread(addThread).start()
);
}
static class AddThread implements Runnable {
// Initializes the I value
private int i = 0;
@Override
public void run(a) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Increment operation
i++;
log.info("Current thread :{}, I :{}", Thread.currentThread().getName(), i); }}}Copy the code
The result is as follows:
[thread-1] infocom.xiaozhi.simple.Add - Current Thread: thread-5, I :4 [thread-1] infocom.xiaozhi.simple.Add - Current Thread: thread-5 Thread-1, I :3 [thread-9] info.xiaozhi.simple.Add [thread-4] infocom.xiaozhi.simple.Add [thread-6] infocom.xiaozhi.simple.Add [thread-0] infocom.xiaozhi.simple.Add Thread-0, I :2 [thread-2] info.xiaozhi.simple.Add [thread-8] infocom.xiaozhi.simple.Add - current Thread: [thread-7] infocom.xiaozhi.simple.Add - Current Thread: Thread-7, I :6 [thread-3] info.xiaozhi.simple. Add - Current Thread: thread-3, I :3Copy the code
It can be seen that the I values of some threads are the same (for example, thread-1 and thread-3), which indicates that threads are unsafe. How to solve this problem?
I believe everyone must have their own solution, here will not post the detailed code, the code has been uploaded code cloud, at the end of the link.
- The core code uses the synchronized modifier
- The core code uses the display Lock Lock package
- Using the JDK’s AtomicInteger class
How about synchronized, Lock, and AtomicInteger?
Then we use the JMH measurement tool introduced in the last article to perform the performance test code is as follows:
@BenchmarkMode(Mode.AverageTime) // Count the average time
@OutputTimeUnit(TimeUnit.MICROSECONDS) // Microseconds
@Warmup(iterations = 10) // Preheat 10 times
@Measurement(iterations = 10) // Measure 10 times
public class SynchronizedVsLockVsAtomicInteger {
@State(Scope.Group)
public static class SynMonitor {
private int i = 0;
public void synInc(a) {
synchronized (this) { i++; }}}@State(Scope.Group)
public static class LockMonitor {
private final Lock lock = new ReentrantLock();
private int i = 0;
public void lockInc(a) {
lock.lock();
try {
i++;
} finally{ lock.unlock(); }}}@State(Scope.Group)
public static class AtomicIntegerMonitor {
private AtomicInteger i = new AtomicInteger(0);
public void inc(a) { i.incrementAndGet(); }}// Define benchmark methods to test the performance of synchronized modifiers
@GroupThreads(10) // Number of threads
@Group("sync") // The thread group name
@Benchmark
public void synInc(SynMonitor synMonitor) {
synMonitor.synInc();
}
// Define benchmark methods to test display Lock performance
@GroupThreads(10)
@Group("lock")
@Benchmark
public void lockInc(LockMonitor lockMonitor) {
lockMonitor.lockInc();
}
// Define a benchmark method to test AtomicInteger performance
@GroupThreads(10)
@Group("atomic")
@Benchmark
public void atomicInc(AtomicIntegerMonitor atomicIntegerMonitor) {
atomicIntegerMonitor.inc();
}
public static void main(String[] args) throws Exception {
Options options = new OptionsBuilder().include(SynchronizedVsLockVsAtomicInteger.class.getSimpleName())
.addProfiler(StackProfiler.class)
.forks(1)
.timeout(TimeValue.seconds(10))
.build();
newRunner(options).run(); }}Copy the code
The benchmark results are as follows:
Benchmark Mode Cnt Score Error Units SynchronizedVsLockVsAtomicInteger. Atomic avgt 10 0.150 + / - 0.007 us/op SynchronizedVsLockVsAtomicInteger. Lock avgt 10. 0.247 + / - 0.005 us/op SynchronizedVsLockVsAtomicInteger sync avgt 10, 1.128 Plus or minus 0.408 us/opCopy the code
It is not difficult to see the performance order: AtomicInteger > display Lock Lock > synchronized keyword
If you’re careful, you’ll notice that stackprofiler. class is configured.
Secondary result "com. Xiaozhi. Simple. SynchronizedVsLockVsAtomicInteger. Atomic: · stack" : 98.8% 1.2% RUNNABLE WAITING Secondary result "com. Xiaozhi. Simple. SynchronizedVsLockVsAtomicInteger. Lock: · stack" : 21.8% 78.2% WAITING RUNNABLE Secondary result "com. Xiaozhi. Simple. SynchronizedVsLockVsAtomicInteger. Sync: · stack" : 79.9% BLOCKED 19.3% RUNNABLE 0.8% WAITINGCopy the code
Observe the thread status statistics above:
The AtomicInteger thread was 98.8% RUNNABLE and not BLOCKED; The BLOCKED state of synchronized was up to 79.9%;
So why is AtomicInteger performing so well? Did you get it all at once?
Atomic Atomic classes
The above explanation so much, but also to show the strength of the protagonist today.
-
Do you know the atomic types in the JDK?
Here are a few that you might use during development:
- AtomicInteger
- AtomicLong
- AtomicBoolean
- AtomicReference
- AtomicStampedReference
- AtomicArray
- AtomicFieldUpdater
-
How and in what scenarios are so many atomic types used?
Today we are going to talk about AtomicInteger, AtomicLong in detail
AtomicInteger
Like int’s reference type Integer, it inherits from Number; But AtomicInteger also provides many atomic operations.
AtomicInteger has an internal member variable value that is modified by the volatile keyword. In fact, AtomicInteger provides methods that operate primarily on that variable value.
The following is an excerpt from the source code:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
//volatile modifies our value
private volatile int value;
}
Copy the code
-
Create AtomicInteger
1Create AtomicInteger AtomicInteger =new AtomicInteger(); 2Create AtomicInteger AtomicInteger =new AtomicInteger(1234567); Copy the code
In fact, a no-parameter construct is equivalent to a parameterized construct passing in a value of 0
-
Incremental operation
Operations such as I ++ or I = I + 1 are non-atomic, so we can use the AtomicInteger method of Incremental operations
int getAndIncrement()
Returns the current value and increments it
@Test public void getAndIncrement(a) { final AtomicInteger ai = new AtomicInteger(18); assert 18 == ai.getAndIncrement(); assert 19 == ai.get(); } Copy the code
int incrementAndGet()
Returns the incremented value directly
@Test public void incrementAndGet(a) { final AtomicInteger ai = new AtomicInteger(18); assert 19 == ai.incrementAndGet(); assert 19 == ai.get(); } Copy the code
-
Decremental operation
I — or I = i-1 is also non-atomic, so we can also take advantage of atomic Decremental in AtomicInteger
int getAndDecrement()
Returns the current value and decrement it
@Test public void getAndDecrement(a) { final AtomicInteger ai = new AtomicInteger(18); assert 18 == ai.getAndDecrement(); assert 17 == ai.get(); } Copy the code
int decrementAndGet()
Returns the decrement value directly
@Test public void decrementAndGet(a) { final AtomicInteger ai = new AtomicInteger(18); assert 17 == ai.decrementAndGet(); assert 17 == ai.get(); } Copy the code
-
Atomically update value values
boolean compareAndSet(int expect, int update)
Expect represents the current AtomicInteger value, update represents the value to be set, and this method returns a Boolean result
When the expect value is inconsistent with the current value, the modification fails and false is returned
Note: Boolean weekCompareAndSet(int expect, int update) is the same as the current method
@Test public void compareAndSet(a) { final AtomicInteger ai = new AtomicInteger(18); // The incoming value is inconsistent with the ai value assert! ai.compareAndSet(123.24); assert 18 == ai.get(); // The incoming value is consistent with the ai value assert ai.compareAndSet(18.24); assert 24 == ai.get(); } Copy the code
int getAndAdd(int delta)
Return the current value directly, and then add the value to the delta
This method is actually atomic operation based on spin +CAS algorithm
@Test public void getAndAdd(a) { final AtomicInteger ai = new AtomicInteger(18); assert 18 == ai.getAndAdd(13); assert 31 == ai.get(); } Copy the code
int addAndGet(int delta);
Add the value to the delta value and return it directly
@Test public void addAndGet(a) { final AtomicInteger ai = new AtomicInteger(18); assert 31 == ai.addAndGet(13); assert 31 == ai.get(); } Copy the code
-
AtomicInteger and functional interfaces
Functional interfaces were introduced in JDK1.8, and AtomicInteger also provides support for functional interfaces
int getAndUpdate(IntUnaryOperator updateFunction)
int updateAndGet(IntUnaryOperator updateFunction)
@Test public void getAndUpdate02(a) { final AtomicInteger ai = new AtomicInteger(18); // Pass in a lambda expression assert 18 == ai.getAndUpdate(i -> i * 3); assert 54 == ai.get(); } @Test public void updateAndGet02(a) { final AtomicInteger ai = new AtomicInteger(18); // Pass in a lambda expression assert 54 == ai.updateAndGet(i -> i * 3); assert 54 == ai.get(); } Copy the code
int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction)
int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction)
@Test public void getAndAccumulate(a) { final AtomicInteger ai = new AtomicInteger(18); // Use lambda expressions assert 18 == ai.getAndAccumulate(14, Integer::sum); assert 32 == ai.get(); } @Test public void accumulateAndGet(a) { final AtomicInteger ai = new AtomicInteger(18); assert 35 == ai.accumulateAndGet(17, Integer::sum); assert 35 == ai.get(); } Copy the code
When AtomicInteger supports functional interfaces, we should be extremely happy that we can DIY our methods and implement them atomically
Custom functional interfaces are as follows:
@Test public void getAndAccumulate02(a) { final AtomicInteger ai = new AtomicInteger(18); assert 18 == ai.getAndAccumulate(14.new IntBinaryOperator() { @Override public int applyAsInt(int left, int right) { assert 18 == left; assert 14 == right; // Add the two values int temp = left + right; // The result is enlarged 7 times for return return temp * 7; }});// Get the value of the custom functional operation assert 224 == ai.get(); } Copy the code
-
AtomicInteger has two more methods
void set(int value)
void lazySet(int value)
Both methods achieve the same effect, modifying the value of AtomicInteger directly
The set method’s modification of a volatile value is forced to flush to main memory so that other threads can see it immediately, again as a memory barrier underlying the volatile keyword.
Memory barriers, while lightweight enough, still incur performance overhead.
When a set operation is performed in a single thread, there is no need to retain this memory barrier mechanism, so the lazySet method was born!
JVM developers have taken performance to the extreme. Here admire, admire!
For detailed results of the performance tests for both methods, see the cloud code link at the end of this article.
AtomicLong
Since the AtomicLong and AtomicInteger methods are basically the same, I won’t repeat them in this article.
Since the last article JMH issued, some friends said I was too long!
I was a little confused, and it turned out that my article was a little long; Ah, there is no way, in order to explain knowledge in detail, how can you take a quick look at the hasty thing?
But it’s too long and you can’t stand it, so let’s simplify it a little bit.
insider
After the long process above, we learned the use of AtomicInteger.
Now let’s take a look at the inside story of AtomicInteger!
When I first introduced AtomicInteger, I posted a snippet of the source code. Let’s look at it again:
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
Copy the code
Unsafe was written in C++, with lots of assembly CPU instruction code inside
ValueOffset Memory offset for storing a value
Let’s look at the source of the compareAndSet method – CAS
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
Copy the code
**unsafe.compareAndSwapInt(this, valueOffset, expect, update); ** is a native method, which can ensure that our operation is atomic. It will compare the incoming value with the current value. When the expect value is inconsistent with the current value, the modification will fail.
AddAndGet and other methods are also native operations, but they do not return failed operations. While the result of the spin.
AtomicLong underlying also have unsafe.com pareAndSwapIntLong method
Since native method is relatively low-level and decompiling is troublesome, MY knowledge is limited, so I will not go further!
Today AtomicInteger, AtomicLong learning here, I believe that we are relatively clear about this knowledge; I will update the other atomic classes in a later article.
Public Portal — AtomicInteger for Concurrent Programming
The code cloud code link is as follows:
Gitee.com/songyanzhi/…
Thanks for reading.
I wish you all a happy work and good health!