線程安全性:java
當多個線程訪問某個類時,無論運行時環境採用何種調度方式或者這些進程將如何交替執行,而且在主調代碼中不須要任何額外的同步或協同,這個類都能表現出正確的行爲,那麼就稱這個類是線程安全的。編程
線程安全體如今三個方面:數組
使用AtomicInteger保證該變量操做的原子性安全
public class CountExample2 { // 請求總數 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.get()); } private static void add() { count.incrementAndGet(); //至關於++x; // count.getAndIncrement(); //至關於x++ } }
原理:AtomicInteger的incrementAndGet()方法裏邊用到了一個unsafe的類多線程
public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
繼續深刻點進去看getAndAddInt的實現:併發
// public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
這裏最重要的一個方法是:compareAndSwapInt(),這是java底層的一個方法,它不是經過java實現的:app
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
參數解釋:編輯器
Object var1:所操做的對象,好比本次案例中,這個Obect是AtomicInteger count;高併發
long var2:這個對象當前的值;性能
int var4:當前對象要增長的值,好比本次案例中作+1操做,那麼var4就是1;
int var5:調用底層獲得的一個值,若是沒有其餘線程過來操做,這個值應該是等於var2
getAndAddInt()方法中compareAndSwapInt()方法執行解釋:若是對於var1這個對象,若是var2與從底層獲取的值var5是相同的,那麼就執行var5 + var4;
進一步解釋:count的當前值,是當前線程中的值,屬於線程中的工做內存中的值,而底層獲取的值是主存中值,只有當工做內存中的值和主存中的值是一致的時候,才能夠修改。
AtomicLong、LongAdder
在上邊的例子中,把AtomicInteger 替換成AtomicLong,整個方法依然是線程安全的。
第二種方式是使用LongAdder:
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); 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(); } }
AtomicLong和LongAdder的對比:
AtomicLong:該類底層實現是在一個死循環內,不斷的嘗試修改目標值,直到修改爲功,在競爭不激烈的狀況下,修改爲功機率很大,在競爭激烈狀況下修改失敗的機率較大,這種狀況下會有損性能。
LongAdder:因爲Long、Double類型的值JVM容許將他們64位的讀寫操做分拆成32位的讀寫操做,根據此原理 LongAdder將操做的數值分拆成數組,而後最終獲得的是數組的加和,經過分拆均衡操做壓力,所以其性能相對較好
使用場景的選擇:在高併發計數的情景下優先使用LongAdder,其餘狀況使用AtomicLong
**AtomicBoolean **
底層實現的方法是:
public final boolean compareAndSet(boolean expect, boolean update) { int e = expect ? 1 : 0; int u = update ? 1 : 0; return unsafe.compareAndSwapInt(this, valueOffset, e, u); }
這個方法是指某個代碼塊邏輯值執行一次。
使用案例(該案例演示了某一段代碼在多線程狀況下,只執行了一次):
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"); } } }
AtomicReference、AtomicReferenceFieldUpdater
AtomicReference使用示例:
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()); //4 } }
AtomicReferenceFieldUpdater使用示例:
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()); } } }
AtomicStampedReference:解決CAS的ABA問題
ABA問題:在CAS操做的時候,其餘線程將變量的值A改爲了B,可是又改回了A,本線程使用指望值A與當前變量進行比較的時候,發現變量A沒有變,因而CAS將A值進行了交換操做。
解決思路:每次變量更新的時候,把版本號+1
核心類:
AtomicStampedReference
其中的核心方法:compareAndSet()
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }
AtomicLongArray
這個類維護的是一個數組
這個類與AtomicLong比較,方法 裏多了一個索引值讓咱們指定。
synchronized:
修飾代碼塊:大括號括起來的代碼,做用於調用的對象
修飾方法:整個方法,做用於調用的對象
修飾靜態方法:整個靜態方法,做用於全部對象
修飾類,括號括起來的部分,做用於全部對象
synchronized:不可中斷鎖,適合競爭不激烈,可讀性好
Lock:可中斷鎖,多樣化同步,競爭激烈時能維持常態
Atomic:競爭激烈時能維持常態,比Lock性能好;只能同步一個值
致使共享變量在線程間不可見的緣由
**可見性——**synchronized
JMM關於synchronized的兩條規定:
- 線程解鎖前,必須把共享變量的最新值刷新到主內存
- 線程加鎖時,將清空工做內存中共享變量的值,從而使用共享變量時須要從主內存中從新讀取最新的值(注意:加鎖和解鎖是同一把鎖)
**可見性——**volatile
經過假如內存屏障和禁止重排序優化來實現
- 對volatile變量寫操做時,會在寫操做後加入一條store屏障指令,將本地內存中的共享變量值刷新到主內
- 對volatileb變量讀操做時,會在讀操做前加入一條load屏障指令,從主內存中讀取共享變量
volatile關鍵字不具備原子性
適合的場景:
所以volatile特別適合狀態標記量
java內存模型中,容許編輯器和處理器對指令進行重排序,可是重排序過程不會影響到單線程程序的執行,卻會影響到多線程併發執行的正確性。
一般狀況下能夠經過如下三個關鍵字來保證有序性:
happens-before原則
若是兩個操做的執行次序沒法從happens-before原則推導出來,那麼就不能保證他們的有序性,虛擬機就能夠對他們隨意的進行重排序。
也就是除了下面這些規則規定的場景,其餘場景,虛擬機能夠對其進行重排序。
程序次序規則:一個線程內,按照代碼順序,書寫在前面的操做先行發生於書寫在後面的操做
鎖定規則:一個unLock操做先行發生於後面對同一個鎖的lock操做
volatile變量規則:對一個變量的寫操做先行發生於後面對這個變量的讀操做
傳遞規則:若是操做A先行發生於操做B,而操做B又先行發生於操做C,則能夠得出操做A先行發生於操做C
線程啓動規則:Thread對象的start()方法先行發生於此線程的每個動做
線程中斷原則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生。
線程終結規則:線程中全部的操做都先行發生於線程的終止檢測,咱們能夠經過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行。
對象終結規則:一個對象的初始化完成先行發生於它的finalize()方法的開始