Java Review - 并发编程_原子操作类原理剖析
文章目錄
- 概述
- 原子變量操作類
- 主要方法
- incrementAndGet 、decrementAndGet 、getAndIncrement、getAndDecrement
- boolean compareAndSet(long expect, long update)
- 小Demo
- 小結(jié)
概述
JUC包提供了一系列的原子性操作類,這些類都是使用非阻塞算法CAS實(shí)現(xiàn)的,相比使用鎖實(shí)現(xiàn)原子性操作這在性能上有很大提高。
由于原子性操作類的原理都大致相同,我們以AtomicLong類的實(shí)現(xiàn)原理為例,并探討JDK8新增的 LongAdder和LongAccumulator類的原理
原子變量操作類
JUC并發(fā)包中包含有AtomicInteger、AtomicLong和AtomicBoolean等原子性操作類
AtomicLong是原子性遞增或者遞減類,其內(nèi)部使用Unsafe來(lái)實(shí)現(xiàn),我們看下面的代碼
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實(shí)例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 實(shí)際變量值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)通過(guò)Unsafe.getUnsafe()方法獲取到Unsafe類的實(shí)例
為何能通過(guò)Unsafe.getUnsafe()方法獲取到Unsafe類的實(shí)例?其實(shí)這是因?yàn)锳tomicLong類也是在rt.jar包下面的,AtomicLong類就是通過(guò)BootStarp類加載器進(jìn)行加載的。
-
代碼(5)中的value被聲明為volatile的,這是為了在多線程下保證內(nèi)存可見性,value是具體存放計(jì)數(shù)的變量。
-
代碼(2)(4)獲取value變量在AtomicLong類中的偏移量。
主要方法
incrementAndGet 、decrementAndGet 、getAndIncrement、getAndDecrement
【JDK8+】
//(6)調(diào)用 Insafe方法,原子性設(shè)置 value值為原始值+1,返回值為遞增后的值public final long incrementAndGet() {return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;}//(7)調(diào)用 unsafe方法,原子性設(shè)置va1ue值為原始值-1,返回值為遞減之后的值public final long decrementAndGet() {return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;} //(8)調(diào)用 unsafe方法,原子性設(shè)置va1ue值為原始值+1,返回值為原始值 public final long getAndIncrement() {return unsafe.getAndAddLong(this, valueOffset, 1L);} //(9)調(diào)用 unsafe方法,原子性設(shè)置va1ue值為原始值-1,返回值為原始值public final long getAndDecrement() {return unsafe.getAndAddLong(this, valueOffset, -1L);}我們可以發(fā)現(xiàn)這幾個(gè)方法內(nèi)部都是通過(guò)調(diào)用Unsafe的getAndAddLong方法來(lái)實(shí)現(xiàn)操作,這個(gè)函數(shù)是個(gè)原子性操作。
第一個(gè)參數(shù)是AtomicLong實(shí)例的引用, 第二個(gè)參數(shù)是value變量在AtomicLong中的偏移值, 第三個(gè)參數(shù)是要設(shè)置的第二個(gè)變量的值 。
getAndIncrement方法在JDK 7中的實(shí)現(xiàn)邏輯為
public final long getAndIncrement(){while(true){long current=get();long next= current + 1;if (compareAndSet(current, next))return current} }在如上代碼中,每個(gè)線程是先拿到變量的當(dāng)前值(由于value是volatile變量,所以這里拿到的是最新的值),然后在工作內(nèi)存中對(duì)其進(jìn)行增1操作,而后使用CAS修改變量的值。如果設(shè)置失敗,則循環(huán)繼續(xù)嘗試,直到設(shè)置成功。
而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中的循環(huán)邏輯已經(jīng)被JDK 8中的原子操作類UNsafe內(nèi)置了,之所以內(nèi)置應(yīng)該是考慮到這個(gè)函數(shù)在其他地方也會(huì)用到,而內(nèi)置可以提高復(fù)用性。
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);}我們可以看到內(nèi)部還是調(diào)用了unsafe.compareAndSwapLong方法。如果原子變量中的value值等于expect,則使用update值更新該值并返回true,否則返回false。
小Demo
線程使用AtomicLong統(tǒng)計(jì)0的個(gè)數(shù)的例子
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)創(chuàng)建Long型原子計(jì)數(shù)器private static AtomicLong atomicLong = new AtomicLong();//(11)創(chuàng)建數(shù)據(jù)源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統(tǒng)計(jì)數(shù)組arrayOne中0的個(gè)數(shù)Thread threadOne = new Thread(() -> {int size = arrayOne.length;for (int i = 0; i < size; ++i) {if (arrayOne[i].intValue() == 0) {atomicLong.incrementAndGet();}}});//(13)線程two統(tǒng)計(jì)數(shù)組arrayTwo中0的個(gè)數(shù)Thread threadTwo = new Thread(() -> {int size = arrayTwo.length;for (int i = 0; i < size; ++i) {if (arrayTwo[i].intValue() == 0) {atomicLong.incrementAndGet();}}});//(14)啟動(dòng)子線程threadOne.start();threadTwo.start();//(15)等待線程執(zhí)行完畢threadOne.join();threadTwo.join();System.out.println("count 0:" + atomicLong.get());}}
兩個(gè)線程各自統(tǒng)計(jì)自己所持?jǐn)?shù)據(jù)中0的個(gè)數(shù),每當(dāng)找到一個(gè)0就會(huì)調(diào)用AtomicLong的原子性遞增方法
小結(jié)
在沒有原子類的情況下,實(shí)現(xiàn)計(jì)數(shù)器需要使用一定的同步措施,比如使用synchronized關(guān)鍵字等,但是這些都是阻塞算法,對(duì)性能有一定損耗,而這里我們介紹的這些原子操作類都使用CAS非阻塞算法,性能更好。
但是在高并發(fā)情況下AtomicLong還會(huì)存在性能問題。JDK 8提供了一個(gè)在高并發(fā)下性能更好的LongAdder類,且聽下篇分解
總結(jié)
以上是生活随笔為你收集整理的Java Review - 并发编程_原子操作类原理剖析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java Review - 并发编程_T
- 下一篇: Java Review - 并发编程_原