微信公眾號:Zhongger
我是Zhongger,一個在網際網路行業摸魚寫代碼的打工人!
關注我,了解更多你不知道的【Java後端】打工技巧、職場經驗、生活感悟等
java.util.concurrent 包裡提供了一系列的原子性操作類,這些類都是使用CAS機制實現的,相當於使用鎖實現原子性操作在性能上有了很大的提高。由於原子性操作類的原理都大致相同,所以本文主要對AtomicLong類的常用方法進行分析。
二、AtomicLongAtomicLong是原子性遞增或者遞減類,其內部使用Unsafe類來實現。
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).存放變量value的偏移量
private static final long valueOffset;
//(3).native方法來判斷JVM是否支持Long類型的無鎖CAS
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
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;
public AtomicLong(long initialValue) {
value = initialValue;
}
由(1)處代碼,獲取Unsafe類對象的方法,是採用Unsafe.getUnsafe();獲取到的,因為AtomicLong類同Unsafe類一樣,是在rt.jar包下的,它也是是通過BootStrap類加載器進行加載的。可以在IDE中看下AutomicLong類的位置確認一下:
科普一下,這裡的類加載器涉及到JVM的知識。關於類加載器加載與所加載的類的對應關係見下圖
//(6).調用unsafe的getAndAddLong方法,原子性設置value值為原始值+1,返回遞增後的值
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
//(7).調用unsafe的getAndAddLong方法,原子性設置value值為原始值-1,返回遞減後的值
public final long decrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;
}
//(8).調用unsafe的getAndAddLong方法,原子性設置value值為原始值+1,返回原始值
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
//(9).調用unsafe的getAndAddLong方法,原子性設置value值為原始值-1,返回原始值
public final long getAndDecrement() {
return unsafe.getAndAddLong(this, valueOffset, -1L);
}
上述代碼內部都是通過調用Unsafe的getAndAddLong方法來實現操作,getAndAddLong方法是原子性操作,第一個參數是AtomicLong實例的引用,第二個參數為value在AtomicLong中的偏移量,第三個參數是要設置的第二個變量的值。
簡單看一下getAndAddLong 方法的實現:
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;
}
其中的getLongVolatile和compareAndSwapLong方法都是native方法,交給底層語言去實現。
2、compareAndSet 方法看一下另外一個比較常用的compareAndSet方法:
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
同樣也是調用了compareAndSwapLong方法。如果原子變量中的value值等於expect,則使用update值更新value值並返回true,否則返回false。
3、多線程使用AtomicLong統計0的個數package AtomicLongLearn;
import java.util.concurrent.atomic.AtomicLong;
/**
* Created by Zhong Mingyi on 2020/11/12.
*/
public class AtomicLongTest {
//(10)創建Long型原子計數器
private static AtomicLong atomicLong = new AtomicLong();
//(11)數據源
private static Integer[] arr1 = new Integer[]{0,1,2,3,0,5,6,0,56,0};
private static Integer[] arr2 = new Integer[]{10,1,2,3,0,5,6,0,56,0};
public static void main(String[] args) throws InterruptedException {
//(12)統計arr1中0的個數
Thread thread1 = new Thread(() -> {
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] == 0) {
atomicLong.incrementAndGet();
}
}
});
//(13)統計arr2中0的個數
Thread thread2 = new Thread(() -> {
for (int i = 0; i < arr2.length; i++) {
if (arr2[i] == 0) {
atomicLong.incrementAndGet();
}
}
});
//(14)線程執行
thread1.start();
thread2.start();
//(15)等待線程執行完畢
thread1.join();
thread2.join();
System.out.println("兩個數組中0的個數為:"+atomicLong.get());
}
}
輸出結果如下:
兩個數組中0的個數為:7
如上代碼中的兩個線程各自統計自所持數據中0的個數,每當找到一個0就會調用AtomicLong的原子性遞增方法。
如果沒有原子操作類,多線程下實現計數器需要使用比如synchronized的同步措施,會對性能有一定損耗;但原子操作類都使用基於CAS的非阻塞算法,因此會獲取較好的性能。
但是在高並發情況下,AtomicLong還是會存在性能問題,JDK8提供了一個在高並發下性能更好的原子類,我們下一期再來揭曉~
三、總結本期學習了java.util.concurrent 包裡提供的原子性操作類之一——AtomicLong,它其中的主要方法均使用非阻塞算法CAS實現,這比起使用鎖實現原子性操作在性能上有了很大的提升;然後我們使用了AtomicLong類來實現多線程下統計0的個數的需求,並以此更好來理解AtomicLong類;但是在高並發下,AtomicLong類仍然有一定的性能瓶頸,JDK8中有更好的類去彌補它在高並發下性能的不足。
我是Zhongger,一個在網際網路行業摸魚寫代碼的打工人, 求個【關注】和【在看】,你們的支持是我創作的最大動力,我們下期見~
往期回顧:
闖禍了,我用了一個『<>』操作符引發的線上Bug
Java中的「八鎖」,你知道幾個?
非計算機科班的學生如何系統學習Java後端開發技術棧