Introduction to the
In 1.5 provides Java. Util. Concurrent. The atomic toolkit, under this package all the classes are implemented based on CAS, offers a safe, effective, simple to update a variable way.
In order to adapt variable types, 12 classes are provided under the atomic package, which belong to four types of atomic update methods: atomic update basic type, atomic update data, atomic update reference and atomic update attribute. Inside, the wrapper classes are mostly implemented using Unsafe.
Atomic update base type
To update base types atomically, Atomic provides three classes. AtomicInteger, AtomicBoolean, and AtomicLong respectively. The methods of these three classes are basically the same. Here AtomicInteger is used as an example:
- Int addAndGet(): atomically adds the input number to the value in AtomicInteger and returns the result.
public class AtomicIntegerTest { private static AtomicInteger atomicInteger = new AtomicInteger(0); public static Integer addAndGetDemo(int value){ return atomicInteger.addAndGet(value); } public static void main(String[] args) { for (int i = 0; i < 10; i++) { Integer result = addAndGetDemo(i); System.out.println(result); }}}Copy the code
- Boolean compareAndSet(int expect,int uddate): Set the input value atomically if it is equal to the expected value
- Int incrementAndSet(): increments the original value by 1 and returns the operating value, similar to the incrementAndSet command in Redis. Also decrementAndSet()
- Int getAndAdd(int delta): the original value plus the specified value, and returns the value before modification.
- Int getAndSet(int delta): Changes the original value to the new value and returns the original value.
- Int getAndIncrement(): returns the value before increment. And getAndDecrement()
Atomic update array
To update an element in an array atomically, the Atomic package provides three classes: AtomicIntegerArray, AtomicLongArray, and AtomicReferenceArray. The methods in the above classes are basically similar, except that they operate on different data types. Take the API in AtomicIntegerArray as an example.
Int addAndGet(int I, int delta) int addAndGet(int I, int delta) Return true on success otherwise false Boolean compareAndSet(int I, int expect, int update) DecrementAndGet (int I) decrementAndGet(int I) decrementAndGet(int I) decrementAndGet(int I) DecrementAndGet (int I, int delta) Int getAndDecrement(int I) int getAndDecrement(int I) int increment (int I) int getAndDecrement(int I) GetAndSet (int I, int newValue) getAndSet(int I, int newValue)Copy the code
Public class AtomicIntegerArrayDemo {static int[] value = new int[]{1,2,3}; static AtomicIntegerArray ai = new AtomicIntegerArray(value); Public static void main(String[] args) {system.out.println (ai.getandset (2,6)); public static void main(String[] args) {system.out.println (ai.getandset (2,6)); System.out.println(ai.get(2)); System.out.println(value[2]); }}Copy the code
At this point, you can see that the value obtained from the AtomicIntegerArray is different from the value passed into the array. This is because the array is passed through the constructor, and the AtomicIntegerArray makes a copy of the currently passed array. Therefore, when AtomicIntegerArray makes changes to the internal array elements, the original array is not affected.
Atomic update reference types
As mentioned earlier, only one atomic operation of a shared variable can be guaranteed through CAS; when there are more than one, a lock is required. This problem is addressed in the Atomic package. If you need to update multiple variables, you need to use the three classes in the Atomic package, which are: AtomicReference AtomicMarkableReference AtomicMarkableReference AtomicStampedReference AtomicStampedReference AtomicReference AtomicMarkableReference AtomicMarkableReference AtomicStampedReference
public class AtomicReferenceTest { static class User{ private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public User(String name, int age) { this.name = name; this.age = age; } } public static AtomicReference<User> atomicReference = new AtomicReference<>(); Public static void main(String[] args) {User u1 = new User(" 三",18); User u2 = new User(" 四",19); atomicReference.set(u1); atomicReference.compareAndSet(u1,u2); System.out.println(atomicReference.get().getName()); System.out.println(atomicReference.get().getAge()); }}Copy the code
AtomicMarkableReference can be used to solve the ABA problem in CAS. The usage demonstration is as follows:
public class AtomicMarkableReferenceDemo { static class User{ private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public User(String name, int age) { this.name = name; this.age = age; }} public static void main(String[] args) throws InterruptedException {User u1 = new User(" 中 三", 22); User u2 = new User(" 3 ", 33); // There are only true and false states. AtomicMarkableReference<User> amr = new AtomicMarkableReference<>(u1,false); // When you compare objects, The first parameter is the expected value // the second parameter is the new value // the third parameter is the expected mark value // the fourth parameter is the new mark value System.out.println(amr.compareAndSet(u1,u2,false,true)); System.out.println(amr.getReference().getName()); }}Copy the code
An AtomicStampedReference resolves the ABA problem based on the version number. According to the source code, a Pair object is maintained internally, which records the object reference and timestamp information. Ensure that the timestamp is unique when you use the Pair.
Each reference variable in an AtomicStampedReference has the pair.stamp timestamp to resolve the ABA problem in CAS.
Examples are as follows:
public class AtomicStampedReferenceDemo { private static final Integer INIT_NUM = 1000; private static final Integer UPDATE_NUM = 100; private static final Integer TEM_NUM = 200; private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(INIT_NUM, 1); public static void main(String[] args) { new Thread(() -> { int value = (int) atomicStampedReference.getReference(); int stamp = atomicStampedReference.getStamp(); System.out.println(thread.currentThread ().getName() + ":" + value + "" + stamp); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(atomicStampedReference.compareAndSet(value, UPDATE_NUM, stamp, stamp + 1)){ System.out.println(Thread.currentThread().getName() + " : The current value is: "+ atomicStampedReference getReference () +" version number is: "+ atomicStampedReference. GetStamp ()); }else{system.out.println (" update failed! ); }}, "thread A").start(); New Thread(() -> {// make sure Thread A executes thread.yield (); int value = (int) atomicStampedReference.getReference(); int stamp = atomicStampedReference.getStamp(); System.out.println(thread.currentThread ().getName() + ":" + value + "" + stamp); System.out.println(Thread.currentThread().getName() +" : "+atomicStampedReference.compareAndSet(atomicStampedReference.getReference(), TEM_NUM, stamp, stamp + 1)); System.out.println(Thread.currentThread().getName() + " : The current value is: "+ atomicStampedReference getReference () +" version number is: "+ atomicStampedReference. GetStamp ()); System.out.println(Thread.currentThread().getName() +" : "+atomicStampedReference.compareAndSet(atomicStampedReference.getReference(), INIT_NUM, stamp, stamp + 1)); System.out.println(Thread.currentThread().getName() + " : The current value is: "+ atomicStampedReference getReference () +" version number is: "+ atomicStampedReference. GetStamp ()); }, "thread B").start(); }}Copy the code
Thread A: Current value: 1000 Version: 1 Thread B: Current value: 1000 Version: 1 Thread B: true Thread B: Current value: 200 Version: 2 Thread B: false Thread B: The current value is: 200. The version is: 2. The update fails if the version is different.Copy the code
Atomic update field class
Atomic update field classes are used when a field in a class needs to be updated atomically. Atomic package under three kinds of AtomicIntegerFieldUpdater (Atomic updates integer fields), AtomicLongFieldUpdater (Atomic updates long integer fields), AtomicReferenceFieldUpdater (Atomic updates reference type field )
Atomic update field classes are abstract classes, and each time they are used, a updater must be created using the static method newUpdate. Fields of atomic update classes must use the public volatile modifier.
AtomicIntegerFieldUpdater, for example
public class AtomicIntegerFieldUpdaterTest { static class User{ private String name; public volatile int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public User(String name, int age) { this.name = name; this.age = age; } } private static AtomicIntegerFieldUpdater<User> fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class,"age"); public static void main(String[] args) { User user = new User("zhangsan",18); System.out.println(fieldUpdater.getAndIncrement(user)); System.out.println(fieldUpdater.get(user)); }}Copy the code
New atom class in JDK1.8
LongAdder: long integer atomic class
DoubleAdder: Double floating-point atomic class
LongAccumulator: Similar to LongAdder, but more flexible (passing in a functional interface)
DoubleAccumulator: Similar to DoubleAdder, but more flexible (passing in a functional interface)
LongAdder, for example, provides an API that essentially replaces the original AtomicLong.
LongAdder is similar to AtomicLong, which is an atomic increment or decrement class. AtomicLong already provides non-blocking atomic operations via CAS, which is better than synchronizers that use blocking algorithms, but the JDK development team is not satisfied. Because AtomicLong’s performance is not acceptable to them on very high concurrent requests, AtomicLong uses CAS but keeps trying through an infinite loop of spin locks when CAS fails.
Under high concurrency, N multiple threads operating a variable at the same time will cause a large number of threads CAS failure and then in a spin state, which wastes CPU resources and reduces concurrency. Since AtomicLong performance is reduced because multiple threads compete for the update of a variable at the same time, if a variable is broken into multiple variables, Wouldn’t the performance problem be solved by having the same number of threads competing for multiple resources? Yes, JDK1.8’s LongAdder is the idea.
public class Demo9Compare { public static void main(String[] args) { AtomicLong atomicLong = new AtomicLong(0L); LongAdder longAdder = new LongAdder(); long start = System.currentTimeMillis(); for (int i = 0; i < 100; i++) { new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 10000; j++) { //atomicLong.incrementAndGet(); longAdder.increment(); } } }).start(); } while (Thread.activeCount() > 2) { } System.out.println(atomicLong.get()); System.out.println(longAdder.longValue()); System.out.println(" time: "+ (system.currentTimemillis () -start)); }}Copy the code
According to the test results, longAdder can be greatly optimized compared with atomicLong. Of course, different computers because of CPU, memory and other hardware is different, so the test values are different, but the conclusion is the same.
As can be seen from the above results, LongAdder and AtomicLong are very similar when concurrency is low. However, when concurrency is high, the gap between the two becomes larger and larger.