Java Review - 并发编程_原子操作类原理剖析
文章目錄
- 概述
- 原子變量操作類
- 主要方法
- incrementAndGet 、decrementAndGet 、getAndIncrement、getAndDecrement
- boolean compareAndSet(long expect, long update)
- 小Demo
- 小結
概述
JUC包提供了一系列的原子性操作類,這些類都是使用非阻塞算法CAS實現的,相比使用鎖實現原子性操作這在性能上有很大提高。
由于原子性操作類的原理都大致相同,我們以AtomicLong類的實現原理為例,并探討JDK8新增的 LongAdder和LongAccumulator類的原理
原子變量操作類
JUC并發包中包含有AtomicInteger、AtomicLong和AtomicBoolean等原子性操作類
AtomicLong是原子性遞增或者遞減類,其內部使用Unsafe來實現,我們看下面的代碼
package java.util.concurrent.atomic; import java.util.function.LongUnaryOperator; import java.util.function.LongBinaryOperator; import sun.misc.Unsafe;/*** @since 1.5* @author Doug Lea*/ public class AtomicLong extends Number implements java.io.Serializable {private static final long serialVersionUID = 1927816293512124184L;// 1 獲取Unsafe實例private static final Unsafe unsafe = Unsafe.getUnsafe();// 2 存放變量的偏移量private static final long valueOffset;/*** Records whether the underlying JVM supports lockless* compareAndSwap for longs. While the Unsafe.compareAndSwapLong* method works in either case, some constructions should be* handled at Java level to avoid locking user-visible locks.*/// 3 判斷JVM是否支持Long類型的CASstatic final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();/*** Returns whether underlying JVM supports lockless CompareAndSet* for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS.*/private static native boolean VMSupportsCS8();static {try {// 4 獲取value在AtomicLong中的偏移量valueOffset = unsafe.objectFieldOffset(AtomicLong.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}// 5 實際變量值private volatile long value;/*** Creates a new AtomicLong with the given initial value.** @param initialValue the initial value*/public AtomicLong(long initialValue) {value = initialValue;}............................}-
代碼(1)通過Unsafe.getUnsafe()方法獲取到Unsafe類的實例
為何能通過Unsafe.getUnsafe()方法獲取到Unsafe類的實例?其實這是因為AtomicLong類也是在rt.jar包下面的,AtomicLong類就是通過BootStarp類加載器進行加載的。
-
代碼(5)中的value被聲明為volatile的,這是為了在多線程下保證內存可見性,value是具體存放計數的變量。
-
代碼(2)(4)獲取value變量在AtomicLong類中的偏移量。
主要方法
incrementAndGet 、decrementAndGet 、getAndIncrement、getAndDecrement
【JDK8+】
//(6)調用 Insafe方法,原子性設置 value值為原始值+1,返回值為遞增后的值public final long incrementAndGet() {return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;}//(7)調用 unsafe方法,原子性設置va1ue值為原始值-1,返回值為遞減之后的值public final long decrementAndGet() {return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;} //(8)調用 unsafe方法,原子性設置va1ue值為原始值+1,返回值為原始值 public final long getAndIncrement() {return unsafe.getAndAddLong(this, valueOffset, 1L);} //(9)調用 unsafe方法,原子性設置va1ue值為原始值-1,返回值為原始值public final long getAndDecrement() {return unsafe.getAndAddLong(this, valueOffset, -1L);}我們可以發現這幾個方法內部都是通過調用Unsafe的getAndAddLong方法來實現操作,這個函數是個原子性操作。
第一個參數是AtomicLong實例的引用, 第二個參數是value變量在AtomicLong中的偏移值, 第三個參數是要設置的第二個變量的值 。
getAndIncrement方法在JDK 7中的實現邏輯為
public final long getAndIncrement(){while(true){long current=get();long next= current + 1;if (compareAndSet(current, next))return current} }在如上代碼中,每個線程是先拿到變量的當前值(由于value是volatile變量,所以這里拿到的是最新的值),然后在工作內存中對其進行增1操作,而后使用CAS修改變量的值。如果設置失敗,則循環繼續嘗試,直到設置成功。
而JDK 8中的邏輯為
unsafe.getAndAddLong(this, valueOffset, -1L); public final long getAndAddLong(Object var1, long var2, long var4) {long var6;do {var6 = this.getLongVolatile(var1, var2);} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));return var6;}可以看到,JDK 7的AtomicLong中的循環邏輯已經被JDK 8中的原子操作類UNsafe內置了,之所以內置應該是考慮到這個函數在其他地方也會用到,而內置可以提高復用性。
boolean compareAndSet(long expect, long update)
/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.** @param expect the expected value* @param update the new value* @return {@code true} if successful. False return indicates that* the actual value was not equal to the expected value.*/public final boolean compareAndSet(long expect, long update) {return unsafe.compareAndSwapLong(this, valueOffset, expect, update);}我們可以看到內部還是調用了unsafe.compareAndSwapLong方法。如果原子變量中的value值等于expect,則使用update值更新該值并返回true,否則返回false。
小Demo
線程使用AtomicLong統計0的個數的例子
import java.util.concurrent.atomic.AtomicLong;/*** @author 小工匠* @version 1.0* @description: TODO* @date 2021/11/30 22:52* @mark: show me the code , change the world*/ public class AtomicLongTest {//(10)創建Long型原子計數器private static AtomicLong atomicLong = new AtomicLong();//(11)創建數據源private static Integer[] arrayOne = new Integer[]{0, 1, 2, 3, 0, 5, 6, 0, 56, 0};private static Integer[] arrayTwo = new Integer[]{10, 1, 2, 3, 0, 5, 6, 0, 56, 0};public static void main(String[] args) throws InterruptedException {//(12)線程one統計數組arrayOne中0的個數Thread threadOne = new Thread(() -> {int size = arrayOne.length;for (int i = 0; i < size; ++i) {if (arrayOne[i].intValue() == 0) {atomicLong.incrementAndGet();}}});//(13)線程two統計數組arrayTwo中0的個數Thread threadTwo = new Thread(() -> {int size = arrayTwo.length;for (int i = 0; i < size; ++i) {if (arrayTwo[i].intValue() == 0) {atomicLong.incrementAndGet();}}});//(14)啟動子線程threadOne.start();threadTwo.start();//(15)等待線程執行完畢threadOne.join();threadTwo.join();System.out.println("count 0:" + atomicLong.get());}}
兩個線程各自統計自己所持數據中0的個數,每當找到一個0就會調用AtomicLong的原子性遞增方法
小結
在沒有原子類的情況下,實現計數器需要使用一定的同步措施,比如使用synchronized關鍵字等,但是這些都是阻塞算法,對性能有一定損耗,而這里我們介紹的這些原子操作類都使用CAS非阻塞算法,性能更好。
但是在高并發情況下AtomicLong還會存在性能問題。JDK 8提供了一個在高并發下性能更好的LongAdder類,且聽下篇分解
總結
以上是生活随笔為你收集整理的Java Review - 并发编程_原子操作类原理剖析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java Review - 并发编程_T
- 下一篇: Java Review - 并发编程_原