線程安全性-原子性

線程安全性

定義

當多個線程訪問同一個類時,無論運行時環境採用何種調度方式,不論線程如何交替執行,在主調代碼中不須要額外的協同或者同步代碼時,這個類均可以表現出正確的行爲,咱們則稱這個類爲線程安全的。

線程安全性

  1. 原子性:提供了互斥訪問,同一時刻只能有一個線程來對他進行操做。
  2. 可見性:一個線程對主內存的修改能夠及時被其餘線程觀察到。
  3. 有序性:一個線程觀察其餘線程中的指令順序,因爲指令重排序的存在,該結果通常雜亂無序。

原子性 - Atomic包

  1. AtomicXXX 是經過 CAS(CompareAndSwap)來保證線程原子性 經過比較操做的對象的值(工做內存的值)與底層的值(共享內存中的值)對比是否相同來判斷是否進行處理,若是不相同則從新獲取。如此循環操做,直至獲取到指望的值。

(關於什麼是主內存什麼事工做內存在上篇博客中進行介紹了,不懂的同窗能夠翻一下)示例代碼:java

@Slf4j
public class AtomicExample2 {

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

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    public static AtomicLong count = new AtomicLong(0);

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count.get());
    }

    private static void add() {
        count.incrementAndGet();
        // count.getAndIncrement();
    }
}
  1. LongAdder和DoubleAdder

jdk8中新增的保證同步操做的類,咱們以前介紹了AtomicXXX來保證原子性,那麼爲何還有有LongAdder呢?
說AtomicXXX的實現是經過死循環來判斷值的,在低併發的狀況下AtomicXXX進行更改值的命中率仍是很高的。可是在高併發下進行命中率可能沒有那麼高,從而一直執行循環操做,此時存在必定的性能消耗,在jvm中咱們容許將64位的數值拆分紅2個32位的數進行儲存的,LongAdder的思想就是將熱點數據分離,將AtomicXXX中的核心數據分離,熱點數據會被分離成多個數組,每一個數據都單獨維護各自的值,將單點的並行壓力發散到了各個節點,這樣就提升了並行,在低併發的時候性能基本和AtomicXXX相同,在高併發時具備較好的性能,缺點是在併發更新時統計時可能會出現偏差。在低併發,須要全局惟一,準確的好比id等使用AtomicXXX,要求性能使用LongAdder數組

@Slf4j
public class AtomicExample3 {

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

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    public static LongAdder count = new LongAdder();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);、】【poiuytrewq;'
        
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        count.increment();
    }
}
  1. AtomicReference、AtomicReferenceFieldUpdater
    AtomicReference是給定指定的指望值當指望值與主內存中的值相同而後更新,示例代碼
@Slf4j
public class AtomicExample4 {

    private static AtomicReference<Integer> count = new AtomicReference<>(0);

    public static void main(String[] args) {
        count.compareAndSet(0, 2); // 2
        count.compareAndSet(0, 1); // no
        count.compareAndSet(1, 3); // no
        count.compareAndSet(2, 4); // 4
        count.compareAndSet(3, 5); // no
        log.info("count:{}", count.get());
    }
}
AtomMNBVCXZenceFieldUpdater主要是更新某一個實例對象的一個字段這個字段必須是用volatile修飾同時不能是private修飾的,·157-=·   123444457890-
@Slf4j
public class AtomicExample5 {

    private static AtomicIntegerFieldUpdater<AtomicExample5> updater =
            AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");

    @Getter
    public volatile int count = 100;

    public static void main(String[] args) {

        AtomicExample5 example5 = new AtomicExample5();

        if (updater.compareAndSet(example5, 100, 120)) {
            log.info("update success 1, {}", example5.getCount());
        }

        if (updater.compareAndSet(example5, 100, 120)) {
            log.info("update success 2, {}", example5.getCount());
        } else {
            log.info("update failed, {}", example5.getCount());
        }
    }
}

最後咱們介紹一下使用AtomicBoolean來實現只執行一次的操做,咱們使用private static AtomicBoolean isHappened = new AtomicBoolean(false)來初始化一個具備原子性的一個Boolean的記錄是否已經被執行安全

@Slf4j
public class AtomicExample6 {

    private static AtomicBoolean isHappened = new AtomicBoolean(false);

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

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    test();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("isHappened:{}", isHappened.get());
    }

    private static void test() {
        if (isHappened.compareAndSet(false, true)) {
            log.info("execute");
        }
    }
}

原子性 - 鎖

咱們除了可使用Atomic包還可使用鎖來實現。
  1. synchronize:依賴jvm
  • 修飾代碼塊:適用範圍大括號括起來的代碼,做用於調用的對象
  • 修飾方法:適用範圍整個方法,做用於調用的對象
  • 修飾靜態方法:適用範圍整個靜態方法,做用於全部對象
  • 修飾一個類:適用範圍是括起來的部分,做用於全部對象
  1. Lock:依賴特殊的cpu指令、代碼實現,ReentrantLock
相關文章
相關標籤/搜索