1. 程式人生 > 實用技巧 >突擊併發程式設計JUC系列-原子更新AtomicLong

突擊併發程式設計JUC系列-原子更新AtomicLong

突擊併發程式設計JUC系列演示程式碼地址:
https://github.com/mtcarpenter/JavaTutorial

JavaJDK 1.5開始提供了java.util.concurrent.atomic包(以下簡稱Atomic包),這個包中的原子操作類提供了一種用法簡單、效能高效、執行緒安全地更新一個變數的方式。原子類通過 CAS (compare and swap)volatilenative方法實現,比 synchronized 開銷更小,執行效率更高,在多執行緒環境下,無鎖的進行原子操作。

Atomic包分類

對其進行分類如下:

為了避免一個章節內容過多,導致大家提前下車,會通過幾個章節進行 Atomic 包下面的知識講解。

基本型別

基本型別有AtomicBooleanAtomicIntegerAtomicLong、這 3 個類提供的方法幾乎一模一樣,本章節以 AtomicLong為案例進行講解,提前小劇透為了在後面和LongAdder 進行對比,LongAdderAtomicLong 在面試中也被問到過呢。

AtomicLong 的常用方法如下

方法名 說明
long getAndIncrement() 以原子方式將當前值加1,注意,返回的是舊值。(i++)
long incrementAndGet() 以原子方式將當前值加1,注意,返回的是新值。(++i)
long getAndDecrement()
以原子方式將當前值減 1,注意,返回的是舊值 。(i--)
long decrementAndGet() 以原子方式將當前值減 1,注意,返回的是舊值 。(--i)
long addAndGet(int delta) 以原子方式將輸入的數值與例項中的值(AtomicLong裡的value)相加,並返回結果
long getAndSet(int newValue) 以原子方式設定為newValue的值,並返回舊值
long get() _獲取 AtomicLong 中的值(value)_
boolean compareAndSet(int expect,int update)
如果輸入的數值等於預期值,則以原子方式將該值設定為輸入的值。
void lazySet(int newValue) 最終會設定成newValue,使用lazySet設定值後,可能導致其他執行緒在之後的一小段時間內還是可以讀到舊的值。
........ .........
JDK 1.8 新增
long getAndUpdate(LongUnaryOperator updateFunction) 定函式的結果原子更新當前值,返回上一個值。
long updateAndGet(LongUnaryOperator updateFunction) 使用給定函式的結果原子更新當前值,返回更新的值。 該功能應該是無副作用的,因為嘗試的更新由於執行緒之間的爭用而失敗時可能會被重新應用。
........ ........

溫馨提示:i++++ii----i只是為了幫助大家理解、理解、理解,重要的事情說三遍,並不是底層的實現就是它們喲。

小試牛刀

古人云“是騾子是馬拉出來溜溜“,一段程式碼擼起來,走你。

public class AtomicExample1 {
    /**
     * 初始化為 0
     */
    private static AtomicLong count = new AtomicLong(0);

    private static LongUnaryOperator longUnaryOperator = new LongUnaryOperator() {

        @Override
        public long applyAsLong(long operand) {
            return 1;
        }
    };

    private static LongBinaryOperator longBinaryOperator = new LongBinaryOperator() {
        @Override
        public long applyAsLong(long left, long right) {
            return left + right;
        }
    };

    public static void main(String[] args) {
        // 以原子方式將當前值加1,返回舊值 (i++): 0
        System.out.println("getAndIncrement=" + count.getAndIncrement());
        // 以原子方式將當前值加1,返回新值(++i)  兩次增加 : 2
        System.out.println("incrementAndGet=" + count.incrementAndGet());
        //以原子方式將當前值減少 1,返回舊值 (i--):2
        System.out.println("incrementAndGet=" + count.getAndDecrement());
        //以原子方式將當前值減少 1,返回舊值 (--i):0
        System.out.println("incrementAndGet=" + count.decrementAndGet());
        // 以原子方式將輸入的數值與例項中的值(AtomicLong裡的value)相加,並返回結果
        System.out.println("addAndGet=" + count.addAndGet(10));
        // 以原子方式設定為`newValue`的值,並返回舊值
        System.out.println("getAndSet=" + count.getAndSet(100));
        // 獲取 atomicLong 的 value
        System.out.println("get=" + count.get());

        System.out.println("*********** JDK 1.8 ***********");
        // 使用將給定函式定函式的結果原子更新當前值,返回上一個值
        // count.get() 為 1:返回 1
        System.out.println("getAndUpdate=" + count.getAndUpdate(longUnaryOperator));
        // 返回 applyAsLong 得值
        System.out.println("getAndUpdate=" + count.getAndUpdate(longUnaryOperator));

        // 獲取 atomicLong 的 value
        System.out.println("get=" + count.get());

        // 使用給定函式應用給當前值和給定值的結果原子更新當前值,返回上一個值
        // 返回結果 1,上次結果
        System.out.println("getAndAccumulate=" + count.getAndAccumulate(2, longBinaryOperator));
        // 返回結果 3 ,上次結果 1 + 2
        System.out.println("getAndAccumulate=" + count.getAndAccumulate(2, longBinaryOperator));
        // 獲取 atomicLong 的 value
        System.out.println("get=" + count.get());
    }
}

一串程式碼送給你,執行結果請參考:

getAndIncrement=0
incrementAndGet=2
incrementAndGet=2
incrementAndGet=0
addAndGet=10
getAndSet=10
get=100
*********** JDK 1.8 ***********
getAndUpdate=100
getAndUpdate=1
get=1
getAndAccumulate=1
getAndAccumulate=3
get=5

不安全併發計數

public class AtomicExample2 {

    // 請求總數
    public static int requestTotal = 1000;


    public static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
        long start = System.currentTimeMillis();
        for (int i = 0; i < requestTotal; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                add();
                countDownLatch.countDown();
            }).start();

        }
        countDownLatch.await();
        System.out.println("count=" + count);
        System.out.println("耗時:" + (System.currentTimeMillis() - start));

    }

    private static void add() {
        ++count;
    }
}

懵懂少年是否對 CountDownLatch有疑問嗎?
CountDownLatch 又稱 倒計數器, 也就是讓一個執行緒或者多個執行緒等待其他執行緒結束後再繼續自己的操作,類似加強版 join()

  • countDown: 執行一次, 計數器的數值 -1。
  • await :等待計算器的值為 0,才進行後面的操作,就像一個柵欄一樣。

AtomicLong 實現併發計數

public class AtomicExample3  {

    // 請求總數
    public static int requestTotal = 5000;

    public static AtomicLong count = new AtomicLong(0);


    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
        long start = System.currentTimeMillis();
        for (int i = 0; i < requestTotal; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                add();
                countDownLatch.countDown();
            }).start();

        }
        countDownLatch.await();
        System.out.println("count=" + count.get());
        System.out.println("耗時:" + (System.currentTimeMillis() - start));
        count.addAndGet(200);
        System.out.println("count=" + count.get());

    }

    private static void add() {
        //count.incrementAndGet();
        count.getAndIncrement();
    }
}

走進原始碼

一段段小小的案例演示,已經無法滿足懵懂少年了,那就加餐,加餐,下面分類介紹下面JDk 1.7JDK1.8的底層實現。

在 Jdk1.7 中,AtomicLong 的關鍵程式碼如下:

		
    static {
      try {
        // 獲取記憶體 value 記憶體中的地址  
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
      } catch (Exception ex) { throw new Error(ex); }
    }

 	// 省略其他程式碼.....

    public final long getAndIncrement() {
       for(;;)
            long current = get();
            long next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

    public final boolean compareAndSet(long expect, long update) {
        return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
    }

getAndIncrement()進去乍一看,無限迴圈,這不就如一個痴情男孩一樣,一直等待他的女神回信,不回信一直等啊等。

  • long current = get(); 獲取 AtomicLong中的 value 值。
  • long next = current + 1;: 在當前記錄 + 1。
  • compareAndSet(current, next): 通過 compareAndSet方法來進行原子更新操作,將當前的值跟記憶體中的值進行比較,相等,則記憶體中沒有被修改,直接寫入新的值到主記憶體中,並return true,否則直接return false。

在 Jdk1.8 中,AtomicLong 的關鍵程式碼如下:

/**
     *  原子更新導致值
     *
     * @return 返回舊值
     */
    public final long getAndIncrement() {
        return 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;
    }
  • var1: 需要修改的類物件
  • var2:修改的欄位的記憶體地址
  • var6 是修改前欄位的值,若是沒其餘執行緒修改即與 var2 相等
  • var6+var4: 修改後欄位的值,也就是新值
  • compareAndSwapLong :當欄位實際值和var6值相當的時候,才會設定其為 var6+var4 。
  • this.getLongVolatile(var1, var2):獲取物件obj中偏移量為offset的變數對應 volatile 語義的值。

從上面的程式碼可以看出AtomicLong在 jdk 7 的迴圈邏輯,在 JDK 8 中原子操作類 unsafe 內建了。之所以內建應該是考慮到這個函式在其他地方也會用到,而內建可以提高複用性。


歡迎關注公眾號 山間木匠 , 我是小春哥,從事 Java 後端開發,會一點前端、通過持續輸出系列技術文章與文會友,如果本文能為您提供幫助,歡迎大家關注、點贊、分享支援,我們下期再見!