Preface
When using multi-threaded concurrent programming, we often encounter modification operations on shared variables. At this point we can choose ConcurrentHashMap, ConcurrentLinkedQueue to store data safely. But if it only involves state modification and thread execution order issues, using atomic components starting with Atomic or synchronization components such as ReentrantLock and CyclicBarrier will be a better choice. The principles and usage of them will be introduced one by one below.
- The realization principle of atomic components CAS
- The usage of atomic components such as AtomicBoolean, AtomicIntegerArray,
- The realization principle of the synchronization component
- Usage of synchronization components such as ReentrantLock and CyclicBarrier
Follow the public account, communicate together, search WeChat: sneak forward
github address , thanks star
The realization principle of atomic components CAS
- The underlying implementation of cas can be seen in an article written before: detailed unlocking principle, synchronized, volatile+cas underlying implementation
Application scenario
- It can be used to realize the atomic operation of variables and states in multiple threads
- Can be used to implement synchronization locks (ReentrantLock)
Atomic component
- The atomic operation of atomic components is achieved by using cas to manipulate volatile variables.
- Volatile type variables ensure that when the variable is modified, other threads can see the latest value
cas guarantees that the value modification operation is atomic and will not be interrupted
Basic type atomic class
AtomicBoolean //布尔类型 AtomicInteger //正整型数类型 AtomicLong //长整型类型
Example of use
public static void main(String[] args) throws Exception { AtomicBoolean atomicBoolean = new AtomicBoolean(false); //异步线程修改atomicBoolean CompletableFuture<Void> future = CompletableFuture.runAsync(() ->{ try { Thread.sleep(1000); //保证异步线程是在主线程之后修改atomicBoolean为false atomicBoolean.set(false); }catch (Exception e){ throw new RuntimeException(e); } }); atomicBoolean.set(true); future.join(); System.out.println("boolean value is:"+atomicBoolean.get()); } ---------------输出结果------------------ boolean value is:false
Reference class atomic class
AtomicReference
//加时间戳版本的引用类原子类
AtomicStampedReference
//相当于AtomicStampedReference,AtomicMarkableReference关心的是
//变量是否还是原来变量,中间被修改过也无所谓
AtomicMarkableReference
AtomicReference source below, which defines an internal
volatile V value
, and means VarHandle (particularly subclass FieldInstanceReadWrite) atomicity operation, MethodHandles will help calculate the offset value in the class, the last call Unsafe. In VarHandlepublic final native boolean compareAndSetReference(Object o, long offset, Object expected, Object x)
atomic modify the object Attributespublic class AtomicReference<V> implements java.io.Serializable { private static final long serialVersionUID = -1848883965231344442L; private static final VarHandle VALUE; static { try { MethodHandles.Lookup l = MethodHandles.lookup(); VALUE = l.findVarHandle(AtomicReference.class, "value", Object.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } } private volatile V value; ....
ABA problem
- Thread X prepares to change the value of the variable from A to B, but during this period, thread Y changes the value of the variable from A to C, and then to A; finally, thread X detects that the variable value is A and replaces it with B. But in fact, A is no longer the original A
- The solution is to make the variable a unique type. The value can be added with a version number or a timestamp. If the version number is added, the modification of thread Y becomes A1->B2->A3. At this time, thread X is updated again and it can be judged that A1 is not equal to A3
The implementation of AtomicStampedReference is similar to that of AtomicReference, but its atomically modified variable is
volatile Pair<V> pair;
, and Pair is its internal class. AtomicStampedReference can be used to solve the ABA problempublic class AtomicStampedReference<V> { private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } } private volatile Pair<V> pair;
- If we do not care whether the variable has been modified in the intermediate process, but only care whether the current variable is still the original variable, we can use AtomicMarkableReference
AtomicStampedReference usage example
public class Main { public static void main(String[] args) throws Exception { Test old = new Test("hello"), newTest = new Test("world"); AtomicStampedReference<Test> reference = new AtomicStampedReference<>(old, 1); reference.compareAndSet(old, newTest,1,2); System.out.println("对象:"+reference.getReference().name+";版本号:"+reference.getStamp()); } } class Test{ Test(String name){ this.name = name; } public String name; } ---------------输出结果------------------ 对象:world;版本号:2
Array atom class
AtomicIntegerArray //整型数组 AtomicLongArray //长整型数组 AtomicReferenceArray //引用类型数组
- A final array is initialized inside the array atom class, which treats the entire array as an object, and then calculates the element offset according to the subscript index, and then calls UNSAFE.compareAndSetReference to perform atomic operations. The array is not modified by volatile. In order to ensure that the element type is visible in different threads, the UNSAFE
public native Object getReferenceVolatile(Object o, long offset)
method is used to obtain the element value in real time. Example of use
//元素默认初始化为0 AtomicIntegerArray array = new AtomicIntegerArray(2); // 下标为0的元素,期待值是0,更新值是1 array.compareAndSet(0,0,1); System.out.println(array.get(0)); ---------------输出结果------------------ 1
Attribute atom class
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicReferenceFieldUpdater
- If the operation object is a certain type of attribute, you can use AtomicIntegerFieldUpdater to update atomically, but the attribute of the class needs to be defined as a volatile modified variable to ensure the visibility of the attribute in each thread, otherwise an error will be reported
Example of use
public class Main { public static void main(String[] args) { AtomicReferenceFieldUpdater<Test,String> fieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Test.class,String.class,"name"); Test test = new Test("hello world"); fieldUpdater.compareAndSet(test,"hello world","siting"); System.out.println(fieldUpdater.get(test)); System.out.println(test.name); } } class Test{ Test(String name){ this.name = name; } public volatile String name; } ---------------输出结果------------------ siting siting
accumulator
Striped64
LongAccumulator
LongAdder
//accumulatorFunction:运算规则,identity:初始值
public LongAccumulator(LongBinaryOperator accumulatorFunction,long identity)
- LongAccumulator and LongAdder are both inherited from Striped64. The main idea of Striped64 is similar to ConcurrentHashMap. When the concurrency of calculation of a single variable is slow, we can disperse the mathematical operations on multiple variables. When we need to calculate the total value, we need Add up
- LongAdder is equivalent to a special case implementation of LongAccumulator
Examples of LongAccumulator
public static void main(String[] args) throws Exception { LongAccumulator accumulator = new LongAccumulator(Long::sum, 0); for(int i=0;i<100000;i++){ CompletableFuture.runAsync(() -> accumulator.accumulate(1)); } Thread.sleep(1000); //等待全部CompletableFuture线程执行完成,再获取 System.out.println(accumulator.get()); } ---------------输出结果------------------ 100000
The realization principle of the synchronization component
- Most synchronous components of java maintain a state value internally. Like atomic components, modifying the state value is generally implemented through cas. The maintenance of state modification is abstracted by Doug Lea from AbstractQueuedSynchronizer (AQS) to achieve
- The principle of AQS can be seen in an article written before: detailed unlocking principle, synchronized, volatile+cas bottom layer realization
Synchronization component
ReentrantLock、ReentrantReadWriteLock
- ReentrantLock and ReentrantReadWriteLock are implemented based on AQS (AbstractQueuedSynchronizer). Because they have a distinction between fair lock and unfair lock, they do not directly inherit AQS, but use internal classes to inherit. Fair locks and unfair locks implement AQS respectively, and ReentrantLock and ReentrantReadWriteLock use internal classes to achieve synchronization.
Example of using ReentrantLock
ReentrantLock lock = new ReentrantLock(); if(lock.tryLock()){ //业务逻辑 lock.unlock(); }
Example of using ReentrantReadWriteLock
public static void main(String[] args) throws Exception { ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); if(lock.readLock().tryLock()){ //读锁 //业务逻辑 lock.readLock().unlock(); } if(lock.writeLock().tryLock()){ //写锁 //业务逻辑 lock.writeLock().unlock(); } }
Semaphore implementation principle and usage scenarios
- Semaphore, like ReentrantLock, also has a fair and unfair competition lock strategy. It also implements synchronization by inheriting AQS from internal classes.
- Popular explanation: Suppose there is a well, and there are at most three people to fetch water. Everyone who fetches water needs to occupy a place. When the three positions are all occupied, the fourth person needs to fetch water, and you must wait for one of the first three people to leave the water level before you can continue to get the water fetching position
Example of use
public static void main(String[] args) throws Exception { Semaphore semaphore = new Semaphore(2); for (int i = 0; i < 3; i++) CompletableFuture.runAsync(() -> { try { System.out.println(Thread.currentThread().toString() + " start "); if(semaphore.tryAcquire(1)){ Thread.sleep(1000); semaphore.release(1); System.out.println(Thread.currentThread().toString() + " 无阻塞结束 "); }else { System.out.println(Thread.currentThread().toString() + " 被阻塞结束 "); } } catch (Exception e) { throw new RuntimeException(e); } }); //保证CompletableFuture 线程被执行,主线程再结束 Thread.sleep(2000); } ---------------输出结果------------------ Thread[ForkJoinPool.commonPool-worker-19,5,main] start Thread[ForkJoinPool.commonPool-worker-5,5,main] start Thread[ForkJoinPool.commonPool-worker-23,5,main] start Thread[ForkJoinPool.commonPool-worker-23,5,main] 被阻塞结束 Thread[ForkJoinPool.commonPool-worker-5,5,main] 无阻塞结束 Thread[ForkJoinPool.commonPool-worker-19,5,main] 无阻塞结束
It can be seen that there are three threads. Because the semaphore is set to 2, the third thread cannot obtain information successfully, and will print the end of the block.
CountDownLatch implementation principle and usage scenarios
- CountDownLatch is also a synchronous operation implemented by AQS
- Popular explanation: When playing a game, if the main task needs to complete five small tasks, the main task can continue. At this time, you can use CountDownLatch, the main line task is blocked and waiting, every time a small task is completed, it is done once and the main line cannot be triggered until all five small tasks are executed.
Example of use
public static void main(String[] args) throws Exception { CountDownLatch count = new CountDownLatch(2); for (int i = 0; i < 2; i++) CompletableFuture.runAsync(() -> { try { Thread.sleep(1000); System.out.println(" CompletableFuture over "); count.countDown(); } catch (Exception e) { throw new RuntimeException(e); } }); //等待CompletableFuture线程的完成 count.await(); System.out.println(" main over "); } ---------------输出结果------------------ CompletableFuture over CompletableFuture over main over
CyclicBarrier implementation principle and usage scenarios
- CyclicBarrier relies on
ReentrantLock lock
andCondition trip
attributes to achieve synchronization - Popular explanation: CyclicBarrier needs to block all threads to the await state, and then all threads are awakened for execution. Imagine that there is a railing blocking five sheep. When the five sheep stand together on the railing, the railing will be pulled up. At this time, all the sheep can fly out of the sheep pen.
Example of use
public static void main(String[] args) throws Exception { CyclicBarrier barrier = new CyclicBarrier(2); CompletableFuture.runAsync(()->{ try { System.out.println("CompletableFuture run start-"+ Clock.systemUTC().millis()); barrier.await(); //需要等待main线程也执行到await状态才能继续执行 System.out.println("CompletableFuture run over-"+ Clock.systemUTC().millis()); }catch (Exception e){ throw new RuntimeException(e); } }); Thread.sleep(1000); //和CompletableFuture线程相互等待 barrier.await(); System.out.println("main run over!"); } ---------------输出结果------------------ CompletableFuture run start-1609822588881 main run over! CompletableFuture run over-1609822589880
StampedLock
- StampedLock does not rely on AQS, but maintains multiple state values internally and implements it with cas
- StampedLock has three modes: write mode, read mode, and optimistic read mode
StampedLock's read-write lock can be converted to each other
//获取读锁,自旋获取,返回一个戳值 public long readLock() //尝试加读锁,不成功返回0 public long tryReadLock() //解锁 public void unlockRead(long stamp) //获取写锁,自旋获取,返回一个戳值 public long writeLock() //尝试加写锁,不成功返回0 public long tryWriteLock() //解锁 public void unlockWrite(long stamp) //尝试乐观读读取一个时间戳,并配合validate方法校验时间戳的有效性 public long tryOptimisticRead() //验证stamp是否有效 public boolean validate(long stamp)
Usage example
public static void main(String[] args) throws Exception { StampedLock stampedLock = new StampedLock(); long stamp = stampedLock.tryOptimisticRead(); //判断版本号是否生效 if (!stampedLock.validate(stamp)) { //获取读锁,会空转 stamp = stampedLock.readLock(); long writeStamp = stampedLock.tryConvertToWriteLock(stamp); if (writeStamp != 0) { //成功转为写锁 //fixme 业务操作 stampedLock.unlockWrite(writeStamp); } else { stampedLock.unlockRead(stamp); //尝试获取写读 stamp = stampedLock.tryWriteLock(); if (stamp != 0) { //fixme 业务操作 stampedLock.unlockWrite(writeStamp); } } } }
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。