定義 首先你們須要思考一下何爲線程安全性呢??java
《Java併發編程實戰》書中給出定義:當多個線程訪問某個類時,無論運行時環境採用何種調度方式或者這些線程將如何交替執行,而且在調用代碼中不須要任何額外的同步,這個類都能表現出正確的行爲,那麼這個類就是線程安全的。程序員
對於線程安全性主要從如下幾個方面出發:原子性、有序性、可見性。數據庫
原子性:提供互斥訪問,同一時刻只能有一個線程對數據進行操做; 例如:atomicXXX類,synchronized關鍵字的應用。 編程
有序性:一個線程觀察其餘線程中的指令執行順序,因爲指令重排序,該觀察結果通常雜亂無序;例如,happens-before原則。 安全
可見性:一個線程對主內存的修改能夠及時地被其餘線程看到;例如:synchronized,volatile。markdown
AtomicXxx 多線程
談起原子性確定離不開衆所周知的Atomic包,JDK裏面提供了不少atomic類,AtomicInteger,AtomicLong,AtomicBoolean等等。 併發
以AtomicInteger爲例: app
class AtomicIntegerExample {
private static final Logger log = LoggerFactory.getLogger(AtomicIntegerExample.class);
// 請求總數
public static int requestTotal = 500;
// 併發執行的線程數
public static int threadTotal = 20;
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(requestTotal);
for (int i = 0; i < requestTotal ; 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();
}
}
複製代碼
跟着這個Demo,試着debuge一下,看下底層如何實現的??? 關鍵方法:incrementAndGet()oop
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
複製代碼
AtomicInteger中的incrementAndGet方法就是樂觀鎖的一個實現,使用自旋(循環檢測更新)的方式來更新內存中的值並經過底層CPU執行來保證是更新操做是原子操做。
使用自旋鎖機制便會形成何種問題呢???
若是長時間自旋不成功,則會給CPU帶來很是大的執行開銷。
隨之咱們跟進getAndAddInt方法,即魔法類UnSafe,關於此類,後期小編抽時間也會整理出一篇文章出來,敬請期待吧,,,哈哈~
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;
}
複製代碼
你們先分析一下這個方法的代碼結構:do-while(),而後再理解執行邏輯。
首先經過調用getIntVolatile()方法,使用對象的引用與值的偏移量獲得當前值,而後調用compareAndSwapInt檢測若是obj內的value和expect相等,就證實沒有其餘線程改變過這個變量,那麼就更新它爲update,若是這一步的CAS沒有成功,那就採用自旋的方式繼續進行CAS操做。
對於上面的方法參數須要特殊解釋一下,要否則真的會很懵逼:
compareAndSwapInt()但願達到的目標是對於var1對象,若是當前的值var2和底層的值var5相等,那麼把它更新成後面的值(var5+var4).
但願你們可以理解清楚,更重要的是小編不要理解錯誤了,若是存在問題,但願大佬私信不當之處,及時改正。
原子性底層實現核心思想是:CAS,可是CAS中存在ABA問題。
compareAndSet是首先檢查當前引用是否等於預期引用,而且當前標誌是否等於預期標誌,若是所有相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。
何爲ABA呢???
若是一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,可是實際上卻變化了。這就是CAS的ABA問題。
那面對ABA問題,你們是想着如何解決呢???能夠思考一下數據庫中樂觀鎖機制,版本號。 故JDK引出AtomicStampedReference…
先看下這個類的方法,你們要注意翻譯註釋,理解各個參數的含義
/**
* Atomically sets the value of both the reference and stamp
* to the given update values if the
* current reference is {@code ==} to the expected reference
* and the current stamp is equal to the expected stamp.
*
* @param expectedReference the expected value of the reference
* @param newReference the new value for the reference
* @param expectedStamp the expected value of the stamp
* @param newStamp the new value for the stamp
* @return {@code true} if successful
*/
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)));
}
複製代碼
此方法會檢查當前引用是否等於預期引用,而且當前標誌是否等於預期標誌;
若是所有相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。
可見性
簡單劃下重點: 什麼是線程間的可見性?
一個線程對共享變量值的修改,可以及時的被其餘線程看到。
什麼是共享變量?
若是一個變量在多個線程的工做內存中都存在副本,那麼這個變量就是這幾個線程的共享變量。
什麼是java內存模型?
(Java Memory Model,簡稱JMM) JMM描述了java程序中各類變量(線程共享變量)的訪問規則,以及在JVM中將變量存儲到內存和從內存中讀取出變量這樣的底層細節。
規則1:
1>全部的變量都存儲在主內存中
2>每一個線程都有本身獨立的工做內存,裏面保存該線程使用到的變量的副本(主內存中該變量的一份拷貝)
規則2:
1>線程對共享變量的全部操做都必須在本身的工做內存中進行,不能直接從主內存中讀寫
2>不一樣線程之間沒法直接訪問其餘線程工做內存中的變量,線程間變量的傳遞須要經過主內存來完成。
有序性是指程序在執行的時候,程序的代碼執行順序和語句的順序是一致的。
爲何會出現不一致的狀況呢?—重排序
在Java內存模型中,容許編譯器和處理器對指令進行重排序,可是重排序過程不會影響到單線程程序的執行,卻會影響到多線程併發執行的正確性。
對於有序性,小編以前讀過周志明的《深刻理解Java虛擬機》書中是這樣介紹有序性的: Happends-Before原則
1. 程序次序規則:一個線程內,按照代碼順序,書寫在前面的操做先行發生於書寫在後面的操做;
2.鎖定規則:一個unLock操做先行發生於後面對同一個鎖lock操做;
3.volatile變量規則:對一個變量的寫操做先行發生於後面對這個變量的讀操做;
4.傳遞規則:若是操做A先行發生於操做B,而操做B又先行發生於操做C,則能夠得出操做A先行發生於操做C;
5.線程啓動規則:Thread對象的start()方法先行發生於此線程的每一個一個動做;
6.線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生;
7.線程終結規則:線程中全部的操做都先行發生於線程的終止檢測,咱們能夠經過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行;
8.對象終結規則:一個對象的初始化完成先行發生於他的finalize()方法的開始;
複製代碼
對於線程的可見性和有序性的理解,須要創建Java內存模型在基礎上理解和思考,雖然理解起來有點抽象,每次讀到系列文章,都是能收穫不一樣的知識點,書讀百遍其義自見,哈哈,,,繼續加油吧!!!向每一位正在努力的程序員致敬!!!
出處:blog.csdn.net/xuan\_lu/ar…?
若是你以爲這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
點贊,轉發,有大家的 『點贊和評論』,纔是我創造的動力。
關注公衆號 『 JavaAC 』,不按期分享原創知識。
同時能夠期待後續文章ing🚀
.關注後掃碼便可獲取學習資料包