1. 鎖
為什么要用鎖: 線程進行的過程中隨時都有可能產生線程切換, 如果操作同一個變量,可能就會發生問題: 例如兩個線程對同一個變量i 各自進行10次自增操作, 得到的結果可能就不是20 , 原因是i++不是原子性的, 它是執行了三條語句 Load i i+1 Store i, 在執行Load i之后如果發生了線程切換, 下一個線程執行完了 Load i i++ Store i 這時候 再切換回來, Load 到的i 還是下一個進程Store 之前的i
鎖的實現,
1.屏蔽硬件中斷,
缺點: 1. 需要禁用硬件中斷,如果臨界區執行時間很長,可能會導致其他硬件的任務無法正常執行.比如網絡包,磁盤塊讀寫等
缺點: 2. 兩個CPU的情況下 ,處理起來比較麻煩, 因為CPU自己只能屏蔽自己的響應中斷能力, 要去屏蔽其他CPU的硬件中斷,可能這時候其他CPU已經執行了臨界區代碼
2.軟件方法
P算法,D算法 ,解決兩個線程互斥問題, E M B(bakery) 算法,解決多個線程互斥問題
優點: 只需要軟件就能解決
缺點: 忙等占用CPU資源,實現復雜
3.原子操作
系統提供原子操作CAS Exchange
int CAS(*p, except, update) 如果指針所指的位置與except相同, 則更新為update, 并返回指針所指的位置的值,
如果指針所指的位置與except不同, 則不更新, 并返回 指針所指的位置的值
2.信號量
基于上面3所說的原子操作實現
信號量是可以實現鎖的功能的
3. 管程
鎖 :
條件變量:
進入管程是互斥的
進入之后有很多條件變量, 每個變量都有一個等待隊列, 條件變量是可以釋放鎖的,
鎖的實現在1中已經講過了
條件變量怎么實現:
有wartingCount,和waitingQueue(注意waitingCount 和信號量是有區別的, waitingCount不需要)
wait方法, 和signal方法
wait方法: 釋放鎖 waitingCount++,TCB加入waitingQueue 通知操作系統發起線程調度
signal方法.if waitingCount > 0 { remove Thread t frome waitingQueue , weakup t }
4鎖的種類
樂觀鎖
認為一般情況下沒有其他線程來搶占資源,只是在要更新的時候去判斷一下是否有其他線程修改了資源,
Java中: Atomic系列 CAS機制就是樂觀鎖
悲觀鎖
認為一般情況下總是有其他線程來搶占資源,所以提前加鎖,
JAVA中, synchronize Lock 都是悲觀鎖
獨占鎖
加了鎖之后,只能由一個線程讀取和修改其中的共享資源,synchronize, ReentraindLock 都是獨占鎖
共享鎖
一個線程對一個數據加了共享鎖,其他線程也只能對它加共享鎖不能加獨占鎖,獲得共享鎖的線程只能讀數據,不能修改數據
JAva中 ReentraintReadWriteLock就是共享鎖
互斥鎖
互斥鎖是獨占鎖的一種常規實現,某一資源同時只允許一個線程對它訪問,有唯一性和排他性
讀寫鎖
讀寫鎖是共享鎖的一種實現
獨占寫鎖,共享讀鎖
公平鎖
公平鎖是指多個線程按照申請鎖的順序來獲取鎖,這里類似排隊買票,先來的人先買,后來的人在隊尾排著,這是公平的。
/**
* 創建一個可重入鎖,true 表示公平鎖,false 表示非公平鎖。默認非公平鎖
*/
Lock lock = new ReentrantLock(true);
非公平鎖
非公平鎖是指多個線程獲取鎖的順序并不是按照申請鎖的順序,有可能后申請的線程比先申請的線程優先獲取鎖,在高并發環境下,有可能造成優先級翻轉,或者饑餓的狀態(某個線程一直得不到鎖)。
在 java 中 synchronized 關鍵字是非公平鎖,ReentrantLock默認也是非公平鎖。
/**
* 創建一個可重入鎖,true 表示公平鎖,false 表示非公平鎖。默認非公平鎖
*/
Lock lock = new ReentrantLock(false);
可重入鎖
可重入鎖
又稱之為遞歸鎖
,是指同一個線程在外層方法獲取了鎖,在進入內層方法會自動獲取鎖。
對于Java ReentrantLock而言, 他的名字就可以看出是一個可重入鎖。對于Synchronized而言,也是一個可重入鎖。
敲黑板:可重入鎖的一個好處是可一定程度避免死鎖。
自旋鎖
旋鎖是指線程在沒有獲得鎖時不是被直接掛起,而是執行一個忙循環,這個忙循環就是所謂的自旋。自旋鎖的目的是為了減少線程被掛起的幾率,因為線程的掛起和喚醒也都是耗資源的操作。
如果鎖被另一個線程占用的時間比較長,即使自旋了之后當前線程還是會被掛起,忙循環就會變成浪費系統資源的操作,反而降低了整體性能。因此自旋鎖是不適應鎖占用時間長的并發情況的。
在 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;
}
另外自適應自旋鎖也需要了解一下。
在JDK1.6又引入了自適應自旋,這個就比較智能了,自旋時間不再固定,由前一次在同一個鎖上的自旋時間以及鎖的擁有者的狀態來決定。如果虛擬機認為這次自旋也很有可能再次成功那就會自旋較多的時間,如果自旋很少成功,那以后可能就直接省略掉自旋過程,避免浪費處理器資源。
分段鎖
分段鎖 是一種鎖的設計,并不是具體的一種鎖。
分段鎖設計目的是將鎖的粒度進一步細化,當操作不需要更新整個數組的時候,就僅僅針對數組中的一項進行加鎖操作。
在 Java 語言中 CurrentHashMap 底層就用了分段鎖,使用Segment,就可以進行并發使用了。
5鎖升級(無鎖|偏向鎖|輕量級鎖|重量級鎖)
無鎖
無鎖狀態其實就是上面講的樂觀鎖,這里不再贅述。
偏向鎖
Java偏向鎖(Biased Locking)是指它會偏向于第一個訪問鎖的線程,如果在運行過程中,只有一個線程訪問加鎖的資源,不存在多線程競爭的情況,那么線程是不需要重復獲取鎖的,這種情況下,就會給線程加一個偏向鎖。
偏向鎖的實現是通過控制對象Mark Word的標志位來實現的,如果當前是可偏向狀態,需要進一步判斷對象頭存儲的線程 ID 是否與當前線程 ID 一致,如果一致直接進入。
輕量級鎖
當線程競爭變得比較激烈時,偏向鎖就會升級為輕量級鎖,輕量級鎖認為雖然競爭是存在的,但是理想情況下競爭的程度很低,通過自旋方式等待上一個線程釋放鎖。
重量級鎖
如果線程并發進一步加劇,線程的自旋超過了一定次數,或者一個線程持有鎖,一個線程在自旋,又來了第三個線程訪問時(反正就是競爭繼續加大了),輕量級鎖就會膨脹為重量級鎖,重量級鎖會使除了此時擁有鎖的線程以外的線程都阻塞。
升級到重量級鎖其實就是互斥鎖了,一個線程拿到鎖,其余線程都會處于阻塞等待狀態。
在 Java 中,synchronized 關鍵字內部實現原理就是鎖升級的過程:無鎖 --> 偏向鎖 --> 輕量級鎖 --> 重量級鎖
6 鎖優化
鎖粗化, 鎖住的范圍增大,
鎖消除 編譯過程中發現不需要加鎖的就直接消除同步鎖