執行緒安全性-原子性
執行緒安全性
定義
當多個執行緒訪問同一個類時,不管執行時環境採用何種排程方式,不論執行緒如何交替執行,在主調程式碼中不需要額外的協同或者同步程式碼時,這個類都可以表現出正確的行為,我們則稱這個類為執行緒安全的。
執行緒安全性
- 原子性:提供了互斥訪問,同一時刻只能有一個執行緒來對他進行操作。
- 可見性:一個執行緒對主記憶體的修改可以及時被其他執行緒觀察到。
- 有序性:一個執行緒觀察其他執行緒中的指令順序,由於指令重排序的存在,該結果一般雜亂無序。
原子性 - Atomic包
- AtomicXXX 是通過 CAS(CompareAndSwap)來保證執行緒原子性 通過比較操作的物件的值(工作記憶體的值)與底層的值(共享記憶體中的值)對比是否相同來判斷是否進行處理,如果不相同則重新獲取。如此迴圈操作,直至獲取到期望的值。
(關於什麼是主記憶體什麼事工作記憶體在上篇部落格中進行介紹了,不懂的同學可以翻一下)示例程式碼:
@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(); } }
- 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(); } }
-
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包還可以使用鎖來實現。
- synchronize:依賴jvm
- 修飾程式碼塊:適用範圍大括號括起來的程式碼,作用於呼叫的物件
- 修飾方法:適用範圍整個方法,作用於呼叫的物件
- 修飾靜態方法:適用範圍整個靜態方法,作用於所有物件
- 修飾一個類:適用範圍是括起來的部分,作用於所有物件
- Lock:依賴特殊的cpu指令、程式碼實現,ReentrantLock