目錄
- 定義
- 原子性
- AtomicXxx
- AtomicStampedReference
- 可見性
- 有序性
- 參考資料
定義
首先大家需要思考一下何為執行緒安全性呢???
《Java并發編程實戰》書中給出定義:當多個執行緒訪問某個類時,不管運行時環境采用何種調度方式或者這些執行緒將如何交替執行,并且在呼叫代碼中不需要任何額外的同步,這個類都能表現出正確的行為,那么這個類就是執行緒安全的,
對于執行緒安全性主要從以下幾個方面出發:原子性、有序性、可見性,
原子性:提供互斥訪問,同一時刻只能有一個執行緒對資料進行操作;
例如:atomicXXX類,synchronized關鍵字的應用,
有序性:一個執行緒觀察其他執行緒中的指令執行順序,由于指令重排序,該觀察結果一般雜亂無序;例如,happens-before原則,
可見性:一個執行緒對主記憶體的修改可以及時地被其他執行緒看到;例如:synchronized,volatile,
原子性
AtomicXxx
談起原子性肯定離不開眾所周知的Atomic包,JDK里面提供了很多atomic類,AtomicInteger,AtomicLong,AtomicBoolean等等,
以AtomicInteger為例:
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()
/**
* 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…
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>不同執行緒之間無法直接訪問其他執行緒作業記憶體中的變數,執行緒間變數的傳遞需要通過主記憶體來完成,
對于執行緒可見性大家更多層面是基于Volatile的應用,請大家移步我的另一篇文章【Java執行緒】深入理解Volatile關鍵字和使用
有序性
有序性是指程式在執行的時候,程式的代碼執行順序和陳述句的順序是一致的,
為什么會出現不一致的情況呢?—重排序
在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記憶體模型在基礎上理解和思考,雖然理解起來有點抽象,每次讀到系列文章,都是能識訓不同的知識點,書讀百遍其義自見,哈哈,,,繼續加油吧!!!向每一位正在努力的程式員致敬!!!
參考資料
《Java高并發編程實戰》
《Java并發編程》
多執行緒安全性和Java中的鎖
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/267039.html
標籤:其他
下一篇:瀏覽器哪個好用
