JUC---原子類Atomic*.java源碼解析(JDK13)

java.util.concurrent包系列文章
JUC—ThreadLocal源碼解析(JDK13)
JUC—ThreadPoolExecutor線程池源碼解析(JDK13)
JUC—各種鎖(JDK13)
JUC—原子類Atomic*.java源碼解析(JDK13)
JUC—CAS源碼解析(JDK13)
JUC—ConcurrentHashMap源碼解析(JDK13)
JUC—CopyOnWriteArrayList源碼解析(JDK13)
JUC—併發隊列源碼解析(JDK13)
JUC—多線程下控制併發流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)


一、什麼是原子類

  • 不可分割性
  • 一個操作是不可中斷的,在多線程下也可以保證
  • 相比於鎖,原子變量可以把競爭資源縮小到變量級別,粒度更細。
  • 通常情況下比鎖的效率更高,但是在高度競爭的情況下性能更低。

以AtomicInteger爲例,看看它的方法

int get();//獲取到當前值
int getAndSet(int newValue) {//獲取到當前值並設置新的值
    return U.getAndSetInt(this, VALUE, newValue);
}
int getAndIncrement() {//獲取到當前值並自增
    return U.getAndAddInt(this, VALUE, 1);
}
int getAndDecrement() {//獲取到當前值並自減
    return U.getAndAddInt(this, VALUE, -1);
}
int getAndAdd(int delta) {//獲取到當前值並增加delta
    return U.getAndAddInt(this, VALUE, delta);
}
//最重要的CAS操作:如果當前的值是expectedValue,則設置當前值爲newValue
boolean compareAndSet(int expectedValue, int newValue) {
    return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
}

爲什麼是線程安全的:

public final int getAndIncrement() {
  return U.getAndAddInt(this, VALUE, 1);
}
//getAndIncrement方法就是調用了Unsafe類的getAndAddInt方法,並且通過do while循環自旋CAS。從而保證原子性。
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!weakCompareAndSetInt(o, offset, v, v + delta));
    return v;
}

常用的原子類和原子數組的實例代碼Demo在我的GitHub上:源碼


二、原子引用類AtomicReference

AtomicReference的作用:和AtomicInteger效果一樣,AtomicInteger可以讓一個整數原子性,AtomicReference可以讓一個對象保證原子性。

看看源碼,也是CAS,熟悉的味道:
在這裏插入圖片描述
利用AtomicReference實現自旋鎖:

public class SpinLock {

    private AtomicReference<Thread> sign = new AtomicReference<>();

    public void lock() {
        Thread current = Thread.currentThread();
        // 當前沒有線程獲取到鎖的情況下才會跳出while循環,自旋操作
        while (!sign.compareAndSet(null, current)) {
            System.out.println("自旋獲取失敗,再次嘗試");
        }
    }

    public void unlock() {
        Thread current = Thread.currentThread();
        // 釋放當前線程的鎖
        sign.compareAndSet(current, null);
    }

    public static void main(String[] args) {
        SpinLock spinLock = new SpinLock();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "開始嘗試獲取自旋鎖");
                spinLock.lock();
                System.out.println(Thread.currentThread().getName() + "獲取到了自旋鎖");
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    spinLock.unlock();
                    System.out.println(Thread.currentThread().getName() + "釋放了自旋鎖");
                }
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
    }
}

三、AtomicIntegerFieldUpdater把普通變量升級爲原子變量

public class AtomicIntegerFieldUpdaterDemo implements Runnable{

    static Candidate tom;
    static Candidate peter;

    public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater
            .newUpdater(Candidate.class, "score");

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            peter.score++;
            scoreUpdater.getAndIncrement(tom);
        }
    }

    public static class Candidate {
        volatile int score;
    }

    public static void main(String[] args) throws InterruptedException {
        tom=new Candidate();
        peter=new Candidate();
        AtomicIntegerFieldUpdaterDemo r = new AtomicIntegerFieldUpdaterDemo();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("普通變量:"+peter.score);
        System.out.println("升級後的結果"+ tom.score);
    }
}

四、LongAdder累加器

  • 高併發下LongAdder比AtomicLong效率高,不過本質是空間換時間
  • 競爭激烈的時候,LongAdder把不同線程對應到不同cell上進行修改,降低了衝突概率,利用了多段鎖的概念。提高了併發。

AtomicLong爲什麼更慢:
在這裏插入圖片描述
因爲它每一次操作都需要flush和refresh。每一次修改了值都需要把當前值從線程緩存刷到主內存,再同步到每一個線程的內存。在core1中修改了值,需要把值從local cache flush到共享內存(主內存),再從主內存refresh到每個線程的local cache。

LongAdder的改進:
在這裏插入圖片描述
競爭激烈的情況下,每個線程在自己的內存中計算,互相不干擾,也不需要同步每一次修改過後的值。只需要在計算結束後把每個線程的結果加起來。LongAdder採用分段累加,內部有一個base變量,和一個Cell[]數組。

  • 競爭不激烈的情況直接累加到base變量
  • 競爭激烈的情況,各個線程分散累加到自己的cell[i]槽中
    其本質就是空間換時間,
public void add(long x) {
   Cell[] cs; long b, v; int m; Cell c;
   // 先嚐試casBase
    if ((cs = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true;
        if (cs == null || (m = cs.length - 1) < 0 ||
            (c = cs[getProbe() & m]) == null ||//當前線程分配的cell[]槽
            !(uncontended = c.cas(v = c.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}
    
public long sum() {
   Cell[] cs = cells;
     long sum = base;
     if (cs != null) {
         for (Cell c : cs)
             if (c != null)
                 sum += c.value;
     }
     return sum;
 }

五、Accumulator累加器

適用於大量計算、並行計算的場景。

public class LongAccumulatorDemo {

    public static void main(String[] args) {
        LongAccumulator accumulator = new LongAccumulator((x, y) -> 2 + x * y, 1);
        //LongAccumulator accumulator = new LongAccumulator((x, y) -> Math.max(x, y), 1);
        ExecutorService executor = Executors.newFixedThreadPool(8);
        IntStream.range(1, 10).forEach(i -> executor.submit(() -> accumulator.accumulate(i)));

        executor.shutdown();
        while (!executor.isTerminated()) {

        }
        System.out.println(accumulator.getThenReset());
    }
}

  • 我的公衆號:Coding摳腚
  • 偶爾發發自己最近學到的乾貨。學習路線,經驗,技術分享。技術問題交流探討。
    Coding摳腚
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章