After Java 6, we were not only exposed to lock-related locks, but also to many more optimistic atomic modification operations, in other words, we only need to ensure that the modification is safe at that moment. After corresponding packaging, we can process the concurrent modification of objects and the ABA problem in concurrency, this article describes the implementation and usage of classes in the atomic series, including:
Basic class:Atomicinteger, atomiclong, and atomicboolean;
Reference Type:ABA instances of atomicreference and atomicreference, atomicstampedrerence, and atomicmarkablereference;
Array type:Atomicintegerarray, atomiclongarray, atomicreferencearray
Attribute atomic modifier (Updater): Atomicintegerfieldupdater, atomiclongfieldupdater, atomicreferencefieldupdater
Are you confused when you see so many types of data, because you only need to understand one, and the other methods and usage are similar, the related classes will introduce the differences between them. You just need to pay attention to them when using them.
Before using the atomic series, we need to first know that one thing is the unsafe class, full name: Sun. misc. unsafe. This class contains a large number of operations on the C code, including many direct memory allocation and atomic operation calls. The reason why it is marked as unsafe, is to tell you that a large number of method calls in this will have security risks, you need to be careful to use, otherwise it will lead to serious consequences, such as when the memory is allocated through unsafe, if you specify some areas, some pointers similar to C ++ may cross the border to other processes. However, its usage is not the focus of this article, the focus of this article is that most of the atomic series will be operated based on the following local methods in the unsafe class:
Exchange after comparison of object references. If the exchange succeeds, true is returned. If the exchange fails, false is returned.This switching process is completely Atomic. After the result is calculated on the CPU, It will compare whether the memory result is still the original value. If not, it is considered that it cannot be replaced, because the variable type is volatile, the final data written will be viewed by other threads. Therefore, after a thread is successfully modified, other threads will find that they failed to modify the data.
Parameter 1:Objects of the class where the object is located (concurrency occurs only when the attribute of an object is modified, so the class of the object also has an object)
Parameter 2:This attribute is relatively cheap in this object. In fact, it is compared with memory units. Therefore, the starting position of the attribute is required, the reference is to modify the reference address (the width is usually 4-8 bytes based on the OS, number of VM digits, and parameter configuration), and the int value is to modify the relevant four bytes, long is to modify the relevant 8 bytes.
The unsafe method is also used to get the offset: objectfieldoffset (fieldfield) to get the offset of the attribute in the object. The static variable needs to be obtained through staticfieldoffset (field). The total method of calling is: fieldoffset (fieldfield)
Parameter 3:The original value of the modified reference, used to compare whether the original reference is consistent with the target to be modified.
Parameter 4:The target value to be modified.
public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
# The long operation depends on whether the VM supports long cas, because it is possible that the VM itself does not support long cas. If not, the Operation will change to the lock mode, but now the VMS are basically supported.
public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
We do not recommend that you directly use unsafe to operate atomic variables, but use classes encapsulated in Java to operate atomic variables.
Example code 1: atomicintegertest. Java
Import Java. util. concurrent. atomic. atomicinteger; public class atomicintegertest {/*** common method list * @ see atomicinteger # Get () direct return value * @ see atomicinteger # getandadd (INT) add specified data, returns the data before the change * @ see atomicinteger # getanddecrement () minus 1, returns the data before the decrease * @ see atomicinteger # getandincrement () increase by 1, return the added data * @ see atomicinteger # getandset (INT) to set the specified data, return the data before the setting ** @ see atomicinteger # addandget (INT) after adding the specified data, return the added data * @ see atomicinteger # decrementandget () to reduce 1, and return the reduced value * @ see atomicinteger # incrementandget () to increase by 1, returns the added value * @ see atomicinteger # lazyset (INT). Set ** @ see atomicinteger # compareandset (INT, INT) only when get is used, if the value is added successfully, true is returned. Otherwise, false */public final static atomicinteger test_integer = new atomicinteger (1) is returned. Public static void main (string [] ARGs) throws interruptedexception {final thread [] threads = new thread [10]; for (INT I = 0; I <10; I ++) {final int num = I; threads [I] = new thread () {public void run () {try {thread. sleep (1000);} catch (interruptedexception e) {e. printstacktrace ();} int now = test_integer.incrementandget (); system. out. println ("I Am a thread:" + num + ", I get the value. The added value is:" + now) ;}; threads [I]. start () ;}for (thread t: threads) {T. join ();} system. out. println ("final running result:" + test_integer.get ());}}
The code example simulates multiple concurrent threadsAtomicintegerAdd 1. If the data type is normal, the problem that occurs during the increase is that the data that the two threads may see at the same time is the same data. When the increase is complete and the data is written back, it is also the same data, but the two addition operations should be added in sequence by 1, that is, the operation by adding 2. Even more special cases are that after a thread is added to 3, the other thread writes 2, the more you change, the less you get, that is, you cannot get the correct result. In concurrency, we simulate the counter and use it to obtain the precise counter value, the expected result is 11. You can copy the code and run it. The result is 11, and the output sequence may be different, at the same time, it can be proved that the thread is indeed running concurrently (only during the output, the system is requisitioned. but the final result is indeed 11.
I believe you have some knowledge about the use of atomicinteger. To learn more about how to use it, please refer to the notes for defining the variable location in this Code, and have detailed comments on atomicinteger related methods, you can directly trace the source code and use a simple description to describe the purpose of the method.
ForAtomiclongActuallyAtomicintegerThe only difference is that it processes long data;
ForAtomicbooleanThere are fewer methods. There are two common methods:
Atomicboolean # compareandset (Boolean, Boolean) the first parameter is the original value, the second parameter is the new value to be modified, and true is returned if the modification is successful; otherwise, falseatomicboolean # getandset (Boolean) is returned) try to set a new Boolean value until it is successful, and return the data before the setting.
Because the Boolean value has two values, it is a back-and-forth modification, and many methods for increasing and decreasing actually do not exist. For use, we will list a Boolean concurrent modification, example of successful modification with only one thread:
Instance Code 2: atomicbooleantest. Java
Import Java. util. concurrent. atomic. atomicboolean; public class atomicbooleantest {/*** main method: * @ see atomicboolean # compareandset (Boolean, Boolean) the first parameter is the original value, the second parameter is the new value to be modified. If the modification is successful, true is returned. Otherwise, false * @ see atomicboolean # getandset (Boolean) is returned, and a new Boolean value is set until the modification is successful, return the data before setting */public final static atomicboolean test_boolean = new atomicboolean (); public static void main (string [] ARGs) {for (INT I = 0; I <10; I + +) {New thread () {public void run () {try {thread. sleep (1000);} catch (interruptedexception e) {e. printstacktrace ();} If (test_boolean.compareandset (false, true) {system. out. println ("I succeeded! ") ;}}. Start ();}}}
There are 10 threads here. We asked them to requisition the modification of the Boolean value almost at the same time, and output the successful modification: I succeeded! You will find thatOnly one"I succeeded !", It indicates that the lock effect has been achieved during the requisition process.
Let's take a look at the implementation of several basic types, as we started to talk about unsafe. Just look at the source code. Let's take a look at some source code of atomicinteger, for example:IncrementandgetMethod. The source code is:
public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }
We can see that there is an internal endless loop. Only the compareandset operation is continuously performed until the operation is successful, that is, the modification is basically in the compareandset method, you can see that the related modification methods are implemented in this way. The body of the compareandset method is as follows:
public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}
We can see that the unsafe compareandswapint method is used here. Obviously, this refers to the current object of atomicinteger (this object does not need to be static or final as mentioned above, it doesn't matter), while valueoffset is defined as follows:
private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); }}
We can see that the attribute offset is obtained through the objectfieldoffset method described earlier. Therefore, if you define similar operations, you must note that this attribute cannot be static, otherwise, it cannot be obtained using this method.
The following two parameters are the comparison value and the address of the target object to be modified.
In fact, you can see the atomic series here, you will know about the Java layer, and the rest are special usage and packaging, we just mentioned that the three unsafe methods are nothing more than the difference between addresses and values. There is no essential difference in the memory layer, because the address itself is also a numerical value.
To illustrate this problem, let's first talk about the use of reference:
Let's test a reference, which is the same as the Boolean test method. It also tests multiple threads and only one thread can modify it.
Instance code 1: atomicreferencetest. Java
Import Java. util. concurrent. atomic. atomicreference; public class atomicreferencetest {/*** list of related methods * @ see atomicreference # compareandset (object, object) Comparison setting value, parameter 1: original value, parameter 2: modify the target reference * @ see atomicreference # getandset (object) to change the referenced target to the set parameter until the modification is successful, returns the reference */public final static atomicreference <string> atomic_reference = new atomicreference <string> ("ABC"); public static void main (string [] ARGs) before the modification) {for (INT I = 0; I <100; I ++) {final int num = I; new thread () {public void run () {try {thread. sleep (math. ABS (INT) (math. random () * 100);} catch (interruptedexception e) {e. printstacktrace ();} If (atomic_reference.compareandset ("ABC", new string ("ABC") {system. out. println ("I Am a thread:" + num + ", I got the lock and modified the object! ") ;}}. Start ();}}}
As we expected, there was indeed only one thread to execute, followed by the Code: compareandset, and we found that the call in the source code was:
public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update);}
OK. It is indeed the same as we mentioned above. Now we have encountered a new problem of referencing and modifying. What is the problem? What is the ABA problem? When some processes are in the same direction, that is, repeated processing is not allowed, in some cases, a data is changed from A to B, and a may be changed to a after 0 to N links in the middle. At this time, a cannot be changed to B, this is because the status has changed. For example, if you want to perform a batch of account operations in the bank funds, you need to increase the funds by 20 RMB for a day, that is, the background program will constantly scan whether the funds of these users are in this range, but the number of people who need to be added cannot be increased. If the number is increased by 20, if 10 yuan is taken out and continues to be in this range, you can cash out without limit. This is the ABA problem. Similarly, you can also grab a red envelope or win a prize. For example, you can set a maximum of 3 red envelopes for each person each day, the number of awards at that level.
The method we need to use is not a simple compareandset operation, because it only takes into account physical concurrency, rather than controlling the order in the business logic, in this case, we need to use some ideas of the transaction serial number of the database to solve the problem. If the number of modifications to each object can be remembered, we should first compare the number of modifications before making the changes, this problem is simple. The atomicstampedreference class provides this function. In fact, it only adds a reference and counter in the atomicreference class, in fact, whether the counter is completely controlled by yourself is the result of self-increment. You can also mark the version number in your own way. The following example shows a simple demonstration of the ABA problem:
Instance code 3 (demo of ABA problem simulation code ):
Import Java. util. concurrent. atomic. atomicreference;/*** ABA problem simulation, thread concurrency, leading to ABA problem, solution is to use | atomicmarkablereference * See the corresponding example: atomicstampedreferencetest, atomicmarkablereferencetest * @ author zgyin. XY **/public class atomicreferenceabatest {public final static atomicreference <string> atomic_reference = new atomicreference <string> ("ABC"); public static void main (string [] ARGs) {for (INT I = 0; I <100 ; I ++) {final int num = I; new thread () {public void run () {try {thread. sleep (math. ABS (INT) (math. random () * 100);} catch (interruptedexception e) {e. printstacktrace ();} If (atomic_reference.compareandset ("ABC", "abc2") {system. out. println ("I Am a thread:" + num + ", I got the lock and modified the object! ") ;}}. Start () ;}new thread () {public void run () {While (! Atomic_reference.compareandset ("abc2", "ABC"); system. Out. println ("has been changed to the original value! ") ;}}. Start ();}}
The only difference between the Code and the original example is that a thread is added to change the data to the original value until the modification is successful. Why is it not used directly: the getandset method is required because we want a thread to change it to abc2 and then change it back to ABC;
The result is as follows:
I am a thread: 41. I got the lock and modified the object!
The original value has been changed!
I am a thread: 85. I have obtained the lock and modified the object!
Of course, most of your thread numbers are different from mine. As long as you requisition them, you can find that two threads have modified this string, we think that the bunch of threads that change ABC to abc2 only have one success. Even if other threads change it to ABC during their levy, they cannot be modified.
In this case, we use the class atomicstampedreference to solve this problem:
Example code 4 (atomicstampedreference solves the ABA problem ):
Import Java. util. concurrent. atomic. atomicstampedreference; public class atomicstampedreferencetest {public final static atomicstampedreference <string> atomic_reference = new atomicstampedreference <string> ("ABC", 0); public static void main (string [] ARGs) {for (INT I = 0; I <100; I ++) {final int num = I; Final int stamp = atomic_reference.getstamp (); New thread () {public void run () {try {thread. sleep (Math. ABS (INT) (math. random () * 100);} catch (interruptedexception e) {e. printstacktrace ();} If (atomic_reference.compareandset ("ABC", "abc2", stamp, stamp + 1) {system. out. println ("I Am a thread:" + num + ", I got the lock and modified the object! ") ;}}. Start () ;}new thread () {public void run () {int stamp = atomic_reference.getstamp (); While (! Atomic_reference.compareandset ("abc2", "ABC", stamp, stamp + 1); system. Out. println ("has been changed back to the original value! ") ;}}. Start ();}}
Then we run the program and the result is what we want. We found that only one thread modifying ABC to abc2 was accessed, although it was modified back to the original value, however, other threads will not change ABC to abc2.
Class:AtomicmarkablereferenceAndAtomicstampedreferenceThe function is similar, but the difference is that it describes the relationship between the two States. Generally, there are only two ABA problemsAtomicstampedreferenceThere are multiple statuses, so why?AtomicmarkablereferenceBecause it is more readable in processing or not, andAtomicstampedreferenceIt is too casual to define the status, so it is not easy to read a lot of information such as yes and no. It can be considered as a counter or status list. Java advocates to know its meaning through the class name, therefore, the existence of this class is also necessary. Its definition is to convert data to true | false as follows:
public final static AtomicMarkableReference <String>ATOMIC_MARKABLE_REFERENCE = new AtomicMarkableReference<String>("abc" , false);
Operation:
ATOMIC_MARKABLE_REFERENCE.compareAndSet("abc", "abc2", false, true);
Well, the three types of reference classes are introduced. Next we will start to talk about the array usage of atomic, because we started to talk about simple variables and basic data, and how to operate arrays? If you design it, the atomic array requires that the length cannot be modified. Unlike the rich operations of the Collection class, however, it makes the operations on each element in your array absolutely safe, that is, its refined strength is still to the elements on the array, and you have made a Secondary package, so if you design it, you just need to add a subscript to the original operation. We can simulate an integer array, that is, atomicintegerarray.
Instance Code 5 (atomicintegerarraytest. Java)
Import Java. util. concurrent. atomic. atomicintegerarray; public class atomicintegerarraytest {/*** common method list * @ see atomicintegerarray # addandget (INT, INT) execute addition. The first parameter is the subscript of the array, the second parameter is the added quantity. The added result * @ see atomicintegerarray # compareandset (INT, Int, INT) is returned for comparison and modification. Parameter 1: array subscript, parameter 2: original Value, parameter 3. Modify the target value. If the modification succeeds, true is returned. Otherwise, false * @ see atomicintegerarray # decrementandget (INT) is used as the array subscript to reduce the number of the array by 1, return the reduced data * @ see atomicintegerarray # The incrementandget (INT) parameter is an array subscript, which increases the number of the array by 1, return the added data ** @ see atomicintegerarray # getandadd (INT, INT) is similar to addandget. The difference is that the returned value is the data before the change * @ see atomicintegerarray # getanddecrement (INT) similar to decrementandget, the difference is that the data before the change is returned * @ see atomicintegerarray # getandincrement (INT) is similar to incrementandget. The difference is that the data before the change is returned * @ see atomicintegerarray # getandset, INT) set the number of the corresponding lower mark to the specified value, and the second parameter to the set value, returns the data before the change */private final static atomicintegerarray atomic_integer_array = new atomicintegerarray (New int }); public static void main (string [] ARGs) throws interruptedexception {thread [] threads = new thread [100]; for (INT I = 0; I <100; I ++) {final int Index = I % 10; Final int threadnum = I; threads [I] = new thread () {public void run () {int result = atomic_integer_array.addandget (index, index + 1); system. out. println ("thread number:" + threadnum + ", the original value is:" + (index + 1) + ". The added result is: "+ result) ;}}; threads [I]. start () ;}for (thread: threads) {thread. join ();} system. out. println ("======================================>\ n the execution has been completed, and the result list is as follows: "); For (INT I = 0; I <atomic_integer_array.length (); I ++) {system. out. println (atomic_integer_array.get (I ));}}}
Description of calculation results: 100 concurrent threads, each 10 threads will be concurrently modified by one element in the array, that is, each element in the array will be concurrently modified and accessed by 10 threads, each time you increase the size of the original value, the final output result after the calculation is multiples of 11 of the original value, which is consistent with our expectation, this value is possible if it is not thread-safe.
Corresponding classes:AtomiclongarrayActually andAtomicintegerarrayThe operation method is similar. The biggest difference is that the data type of the operation is long. The same is true for atomicrerencearray, but there are only two methods:
Atomicreferencearray # compareandset (INT, object, object) parameter 1: array subscript; parameter 2: modify the original value comparison; parameter 3: Modify the target value successfully, return true, otherwise, falseatomicreferencearray # getandset (INT, object) parameter 1: array subscript parameter 2: The modified target is successfully modified, and the data before modification is returned.
So far, do you know something about the operations inside the array? As expected, the parameter has an additional subscript. to fully verify this, we can see it in the source code:
public final int addAndGet(int i, int delta) { while (true) { int current = get(i); int next = current + delta; if (compareAndSet(i, current, next)) return next; } }
You can see that the corresponding data is obtained based on get (I), and then the operation is similar to that of normal atomicinteger. The details of the get operation are as follows:
public final int get(int i) { return unsafe.getIntVolatile(array, rawIndex(i));}
Here, the unsafe method is used to obtain (visibility) an int type of data based on volatile. The obtained position is determined by rawindex. Its source code is:
private long rawIndex(int i) { if (i < 0 || i >= array.length) throw new IndexOutOfBoundsException("index " + i); return base + (long) i * scale; }
It can be found that the result is an address location, adding a slap offset to the base, then let's look at the definition of base and scale:
private static final int base = unsafe.arrayBaseOffset(int[].class);private static final int scale = unsafe.arrayIndexScale(int[].class);
It can be found that unsafe provides the acquisition of the location of the array base, because the object has a header, and the array has a length position, the second is obviously the width occupied by an array element, that is, the basic precision. Here we should be able to see the power of unsafe.
The last part of this article is the Updater, which is the modifier. It is an extension of the atomic series. The Atomic series are some objects defined for you. You can use them, however, if it is an object that someone else is using, the original code needs to be changed to the atomic series. If you modify all types to the corresponding object, it is very troublesome, because there will be a lot of code involved, at this time, Java provides an external Updater that can provide atomic-like operations for modifying the object's attributes, that is, its operations on these common attributes are safe under concurrency, respectively:Atomicintegerfieldupdater, atomiclongfieldupdater, atomicreferenceupdaterIn this way, the system will be more flexible, that is, the attributes of those classes only need to control concurrency in some cases, and are not required in many cases, however, they usually have the following restrictions:
Limit 1:Operation targetCannot be staticType. As mentioned earlier, unsafe can be used to guess that the extracted non-static type property offset. If it is a static type, an error will be reported if the corresponding method is not used, the Updater does not use the corresponding method.
Limit 2:Operation targetCannot be finalType, because final cannot be modified.
Limit 3: VolatileThe data type, that is, the data itself is read consistent.
Limit 4:Attribute must beThe region where Updater is located is visible., That isPrivateIf the current class is not visible,ProtectedIf no parent-child relationship exists,DefaultIf not in the samePackageIs invisible.
Implementation Method:Find the property through reflection and operate the property, but it is not set accessable, so it must be a visible property to operate.
Let's take a look at the instance.
Instance Code 6: (atomicintegerfieldupdatertest. Java)
Import Java. util. concurrent. atomic. atomicintegerfieldupdater; public class atomicintegerfieldupdatertest {static Class A {volatile int intvalue = 100;}/*** you can directly access the corresponding variables for modification and processing * conditions: to be in an accessible area, if it is private or writable, the default type and the non-parent protected cannot be accessed * The second access object cannot be a static type variable (because it cannot be calculated when calculating the offset of the attribute), it cannot be a final type variable (because it cannot be modified at all). It must be a common member variable ** method (meaning it is almost the same as atomicinteger, the only difference is that the first parameter requires an object reference.) * @ see atomicintegerfieldupdater # Addandget (object, INT) * @ see atomicintegerfieldupdater # compareandset (object, Int, INT) * @ see parameters # decrementandget (object) * @ see atomicintegerfieldupdater # incrementandget (object) ** @ see atomicintegerfieldupdater # getandadd (object, INT) * @ see atomicintegerfieldupdater # getanddecrement (object) * @ see parameters # getandincrement (object) * @ see atomicintegerfieldu Pdater # getandset (object, INT) */public final static atomicintegerfieldupdater <A> atomic_integer_updater = atomicintegerfieldupdater. newupdater (. class, "intvalue"); public static void main (string [] ARGs) {final A = new A (); For (INT I = 0; I <100; I ++) {final int num = I; new thread () {public void run () {If (atomic_integer_updater.compareandset (A, 100,120) {system. out. println ("I Am a thread:" + num + "I Modified the corresponding value! ") ;}}. Start ();}}}
At this point, you will find that only one thread can modify this data. Other methods are the same as described above, and the implementation function is similar to atomicinteger.
WhileAtomiclongfieldupdaterIn fact, the difference is that the data it operates on is of the long type.
AtomicreferencefieldupdaterThere are few methods, mainlyCompareandsetAndGetandsetThe use of the two methods is defined as follows:
static class A { volatile String stringValue = "abc";}AtomicReferenceFieldUpdater <A ,String>ATOMIC_REFERENCE_FIELD_UPDATER = AtomicReferenceFieldUpdater.newUpdater(A.class, String.class, "stringValue");
We can see that the parameter passed here adds an attribute type, because it references an object and the object itself also has a type.