緩存一致性 MESIjava
給定一個模擬併發訪問的例子,不作任何的處理緩存
CountExample1@Slf4j @NotThreadSafe public class CountExample1 { // 請求總數 public static int clientTotal = 5000; // 同時併發執行的線程數 public static int threadTotal = 200; public static int count = 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); } private static void add() { count++; } }這裏面模擬共有5000個請求,併發請求是200個,而後一次請求就將count加一,運行的話會發現count的值比5000小安全
如今咱們對上面代碼進行修改,將count進行原子性併發
@Slf4j @NotThreadSafe public class AtomicExample1 { // 請求總數 public static int clientTotal = 5000; // 同時併發執行的線程數 public static int threadTotal = 200; public static AtomicInteger count = new AtomicInteger(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); } private static void add() { count.incrementAndGet(); // count.getAndIncrement(); } }這裏count是AtomicInteger類型,咱們使用了incrementAndGet()這個方法,這裏進行源碼分析:app
incrementAndGet()調用了unsafe類的getAndAddInt()方法,三個參數分別是count自身,count當前值,加法運算的加數ide
咱們繼續查看getAndAddInt()的源碼源碼分析
主體實現是do...while...,這裏的getIntVolatile()方法是native方法,它是從主內存中獲取count所表明的數的值;而後是compareAndSwapInt()方法,也是native方法,該方法先會取得var2的值,而後比較var2與var5,也就是當前線程工做內存中的count值 與 主內存中的值,若是相等,就將var5 + var4(也就是1),賦值給工做內存;不然,從新取var5,var2比較。而後返回var5,舊值。ui
CASatom
1)AtomicIntegerFiledUpdater類spa
該類的核心做用是原子性更新一個類指定的一個字段的值,該字段必須是volatile修飾,且非static
AtomicIntegerFieldUpdater@Slf4j @ThreadSafe public class AtomicExample2 { private static AtomicIntegerFieldUpdater<AtomicExample2> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicExample2.class, "count"); @Getter public volatile int count = 100; public static void main(String[] args) { AtomicExample2 example2 = new AtomicExample2(); if(updater.compareAndSet(example2, 100, 120)) { log.info("success, count:{}", example2.getCount()); } if(updater.compareAndSet(example2, 100, 120)) { log.info("success, count:{}", example2.getCount()); } else { log.info("failed, count:{}", example2.getCount()); } } }2)AtomicStampedReference類
解決CAS的ABA問題。什麼是ABA問題,就是一個線程1在拿線程中的當前值爲A與主內存的值比較的時候,另外一個線程2已經將該主存中的值改成B又改回了A,致使線程1的比較成立。但這跟不符合CAS的設計。
因此咱們在一個值每次進行修改的時候,加一個版本號,也就是Stamp,每次再加一個版本號的比較,解決此問題。
3) AtomicBoolean類
保證一段代碼只執行一次
private static AtomicBoolean isHappened = new AtomicBoolean(false); if(isHappened.compareAndSet(false, true)) { // 執行的代碼 }
做用於調用的對象,表示是某一個對象。
解決方法能夠用上面提到的synchronized
還可使用volatile 關鍵字
volatile變量在每次被線程訪問時,都強迫從主內存中讀取該變量的值,在該變量發生變化的時候,會強迫線程將最新的值刷新到主內存。
因此的操做是指令級別的,只須要了解便可
volatile的使用場景:狀態標記量
若是兩個操做的執行順序沒法從happens-before原則推導出來,就沒法保證他們的有序性,JVM就能夠對其進行重排序