1. 鎖
為什么要用鎖: 線程進行的過程中隨時都有可能產(chǎn)生線程切換, 如果操作同一個變量,可能就會發(fā)生問題: 例如兩個線程對同一個變量i 各自進行10次自增操作, 得到的結(jié)果可能就不是20 , 原因是i++不是原子性的, 它是執(zhí)行了三條語句 Load i i+1 Store i, 在執(zhí)行Load i之后如果發(fā)生了線程切換, 下一個線程執(zhí)行完了 Load i i++ Store i 這時候 再切換回來, Load 到的i 還是下一個進程Store 之前的i
鎖的實現(xiàn),
1.屏蔽硬件中斷,
缺點: 1. 需要禁用硬件中斷,如果臨界區(qū)執(zhí)行時間很長,可能會導(dǎo)致其他硬件的任務(wù)無法正常執(zhí)行.比如網(wǎng)絡(luò)包,磁盤塊讀寫等
缺點: 2. 兩個CPU的情況下 ,處理起來比較麻煩, 因為CPU自己只能屏蔽自己的響應(yīng)中斷能力, 要去屏蔽其他CPU的硬件中斷,可能這時候其他CPU已經(jīng)執(zhí)行了臨界區(qū)代碼
2.軟件方法
P算法,D算法 ,解決兩個線程互斥問題, E M B(bakery) 算法,解決多個線程互斥問題
優(yōu)點: 只需要軟件就能解決
缺點: 忙等占用CPU資源,實現(xiàn)復(fù)雜
3.原子操作
系統(tǒng)提供原子操作CAS Exchange
int CAS(*p, except, update) 如果指針?biāo)傅奈恢门cexcept相同, 則更新為update, 并返回指針?biāo)傅奈恢玫闹?
如果指針?biāo)傅奈恢门cexcept不同, 則不更新, 并返回 指針?biāo)傅奈恢玫闹?/p>
2.信號量
基于上面3所說的原子操作實現(xiàn)
信號量是可以實現(xiàn)鎖的功能的
3. 管程
鎖 :
條件變量:
進入管程是互斥的
進入之后有很多條件變量, 每個變量都有一個等待隊列, 條件變量是可以釋放鎖的,
鎖的實現(xiàn)在1中已經(jīng)講過了
條件變量怎么實現(xiàn):
有wartingCount,和waitingQueue(注意waitingCount 和信號量是有區(qū)別的, waitingCount不需要)
wait方法, 和signal方法
wait方法: 釋放鎖 waitingCount++,TCB加入waitingQueue 通知操作系統(tǒng)發(fā)起線程調(diào)度
signal方法.if waitingCount > 0 { remove Thread t frome waitingQueue , weakup t }
4鎖的種類
樂觀鎖
認(rèn)為一般情況下沒有其他線程來搶占資源,只是在要更新的時候去判斷一下是否有其他線程修改了資源,
Java中: Atomic系列 CAS機制就是樂觀鎖
悲觀鎖
認(rèn)為一般情況下總是有其他線程來搶占資源,所以提前加鎖,
JAVA中, synchronize Lock 都是悲觀鎖
獨占鎖
加了鎖之后,只能由一個線程讀取和修改其中的共享資源,synchronize, ReentraindLock 都是獨占鎖
共享鎖
一個線程對一個數(shù)據(jù)加了共享鎖,其他線程也只能對它加共享鎖不能加獨占鎖,獲得共享鎖的線程只能讀數(shù)據(jù),不能修改數(shù)據(jù)
JAva中 ReentraintReadWriteLock就是共享鎖
互斥鎖
互斥鎖是獨占鎖的一種常規(guī)實現(xiàn),某一資源同時只允許一個線程對它訪問,有唯一性和排他性
讀寫鎖
讀寫鎖是共享鎖的一種實現(xiàn)
獨占寫鎖,共享讀鎖
公平鎖
公平鎖是指多個線程按照申請鎖的順序來獲取鎖晶通,這里類似排隊買票铡俐,先來的人先買翅阵,后來的人在隊尾排著紊选,這是公平的舆蝴。
/**
* 創(chuàng)建一個可重入鎖门岔,true 表示公平鎖库车,false 表示非公平鎖睁宰。默認(rèn)非公平鎖
*/
Lock lock = new ReentrantLock(true);
非公平鎖
非公平鎖是指多個線程獲取鎖的順序并不是按照申請鎖的順序襟企,有可能后申請的線程比先申請的線程優(yōu)先獲取鎖嘱么,在高并發(fā)環(huán)境下,有可能造成優(yōu)先級翻轉(zhuǎn)顽悼,或者饑餓的狀態(tài)(某個線程一直得不到鎖)曼振。
在 java 中 synchronized 關(guān)鍵字是非公平鎖,ReentrantLock默認(rèn)也是非公平鎖蔚龙。
/**
* 創(chuàng)建一個可重入鎖冰评,true 表示公平鎖,false 表示非公平鎖木羹。默認(rèn)非公平鎖
*/
Lock lock = new ReentrantLock(false);
可重入鎖
可重入鎖
又稱之為遞歸鎖
甲雅,是指同一個線程在外層方法獲取了鎖坑填,在進入內(nèi)層方法會自動獲取鎖抛人。
對于Java ReentrantLock而言, 他的名字就可以看出是一個可重入鎖。對于Synchronized而言脐瑰,也是一個可重入鎖妖枚。
敲黑板:可重入鎖的一個好處是可一定程度避免死鎖。
自旋鎖
旋鎖是指線程在沒有獲得鎖時不是被直接掛起苍在,而是執(zhí)行一個忙循環(huán)绝页,這個忙循環(huán)就是所謂的自旋荠商。自旋鎖的目的是為了減少線程被掛起的幾率,因為線程的掛起和喚醒也都是耗資源的操作续誉。
如果鎖被另一個線程占用的時間比較長莱没,即使自旋了之后當(dāng)前線程還是會被掛起,忙循環(huán)就會變成浪費系統(tǒng)資源的操作酷鸦,反而降低了整體性能郊愧。因此自旋鎖是不適應(yīng)鎖占用時間長的并發(fā)情況的。
在 Java 中井佑,AtomicInteger 類有自旋的操作,我們看一下代碼:
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
另外自適應(yīng)自旋鎖也需要了解一下眠寿。
在JDK1.6又引入了自適應(yīng)自旋躬翁,這個就比較智能了,自旋時間不再固定盯拱,由前一次在同一個鎖上的自旋時間以及鎖的擁有者的狀態(tài)來決定盒发。如果虛擬機認(rèn)為這次自旋也很有可能再次成功那就會自旋較多的時間,如果自旋很少成功狡逢,那以后可能就直接省略掉自旋過程宁舰,避免浪費處理器資源。
分段鎖
分段鎖 是一種鎖的設(shè)計奢浑,并不是具體的一種鎖蛮艰。
分段鎖設(shè)計目的是將鎖的粒度進一步細(xì)化,當(dāng)操作不需要更新整個數(shù)組的時候雀彼,就僅僅針對數(shù)組中的一項進行加鎖操作壤蚜。
在 Java 語言中 CurrentHashMap 底層就用了分段鎖,使用Segment徊哑,就可以進行并發(fā)使用了袜刷。
5鎖升級(無鎖|偏向鎖|輕量級鎖|重量級鎖)
無鎖
無鎖狀態(tài)其實就是上面講的樂觀鎖,這里不再贅述莺丑。
偏向鎖
Java偏向鎖(Biased Locking)是指它會偏向于第一個訪問鎖的線程著蟹,如果在運行過程中,只有一個線程訪問加鎖的資源梢莽,不存在多線程競爭的情況萧豆,那么線程是不需要重復(fù)獲取鎖的,這種情況下昏名,就會給線程加一個偏向鎖炕横。
偏向鎖的實現(xiàn)是通過控制對象Mark Word的標(biāo)志位來實現(xiàn)的,如果當(dāng)前是可偏向狀態(tài)葡粒,需要進一步判斷對象頭存儲的線程 ID 是否與當(dāng)前線程 ID 一致份殿,如果一致直接進入膜钓。
輕量級鎖
當(dāng)線程競爭變得比較激烈時,偏向鎖就會升級為輕量級鎖卿嘲,輕量級鎖認(rèn)為雖然競爭是存在的颂斜,但是理想情況下競爭的程度很低,通過自旋方式等待上一個線程釋放鎖拾枣。
重量級鎖
如果線程并發(fā)進一步加劇沃疮,線程的自旋超過了一定次數(shù),或者一個線程持有鎖梅肤,一個線程在自旋司蔬,又來了第三個線程訪問時(反正就是競爭繼續(xù)加大了),輕量級鎖就會膨脹為重量級鎖姨蝴,重量級鎖會使除了此時擁有鎖的線程以外的線程都阻塞俊啼。
升級到重量級鎖其實就是互斥鎖了,一個線程拿到鎖左医,其余線程都會處于阻塞等待狀態(tài)授帕。
在 Java 中,synchronized 關(guān)鍵字內(nèi)部實現(xiàn)原理就是鎖升級的過程:無鎖 --> 偏向鎖 --> 輕量級鎖 --> 重量級鎖
6 鎖優(yōu)化
鎖粗化, 鎖住的范圍增大,
鎖消除 編譯過程中發(fā)現(xiàn)不需要加鎖的就直接消除同步鎖