Java中的鎖有很多種,經常會聽到“鎖”這個詞。
猶如每天出門時,????就是一種“鎖”,拿不到????,就進不去了。
Java那麼多種類的鎖,都是按不同标準來分類的。就像商店裡的各種商品,可以按式樣,也可以按顔色或者尺寸。
其實它們都是一種思想。
為什麼會有鎖?
一個程序可以包含多個線程,那麼多個線程就會有競争資源的問題出現,為了互相不打架,就有了鎖的概念了。 一個線程也可以自己完成任務,但就像一個小組可以互相配合、共同完成任務,比一個人要快很多是不是?
分類
來個圖~

1 悲觀 Vs 樂觀
林妹妹比較悲觀,寶玉比較樂觀~
1.1 悲觀鎖
看名字便知,它是悲觀的,總是想到最壞的情況。 鎖也會悲觀,它并不是難過,它隻是很謹慎,怕做錯。
每次要讀data的時候,總是覺得其他人會修改資料,是以先加個????,讓其他人不能改資料,再慢慢讀~
要是你在寫一篇日記,怕别人會偷看了,就加了個打開密碼,别人必須拿到密碼才能打開這篇文章。這就是悲觀鎖了。
應用: synchronized關鍵字和Lock的實作類都是悲觀鎖。
1.2 樂觀鎖
它很樂觀,總是想着最好的情況。 它比較大條,不會太擔心。如果要發生,總會發生,如果不會發生,那就不會。為什麼要擔心那麼多?
每次讀data時,總是樂觀地想沒有其他人會同時修改資料,不用加鎖,放心地讀data。 但在更新的時候會判斷一下在此期間别人有沒有去更新這個資料。
就像和别人共同編輯一篇文章,你在編輯的時候别人也可以編輯,而且你覺得别人不會改動到你寫的部分,那就是樂觀鎖了。
事事無絕對,悲觀也好樂觀也好,沒有絕對的悲觀,也沒有絕對的樂觀。隻是在這個當時,相信,還是不相信。
悲觀 & 樂觀 比較
類型 | 實作 | 使用場景 | 缺點 |
---|---|---|---|
悲觀鎖 | synchronized關鍵字和Lock的實作類 | 适合寫操作多的場景,可以保證寫操作時資料正确 | 如果該事務執行時間很長,影響系統的吞吐量 |
樂觀鎖 | 無鎖程式設計,CAS算法 | 适合讀操作多的場景,能夠大幅提升其讀操作的性能 | 如果有外來事務插入,那麼就可能發生錯誤 |
應用
樂觀鎖 —— CAS(Compare and Swap 比較并交換)
是樂觀鎖的一種實作方式。
簡單來說,有3個三個操作數:
- 需要讀寫的記憶體值 V。
- 進行比較的值 A。
- 要寫入的新值 B。
2 公平 Vs 非公平
沒有絕對的公平,也沒有絕對的不公平。
公平,就是按順序排隊嘛。 公平鎖維護了一個隊列。要擷取鎖的線程來了都排隊。
非公平,上來就想搶到鎖,好像一個不講道理的,搶不到的話,隻好再去乖乖排隊了。 非公平鎖沒有維護隊列的開銷,沒有上下文切換的開銷,可能導緻不公平,但是性能比fair的好很多。看這個性能是對誰有利了。
舉個栗子
3 可重入鎖 Vs 非可重入鎖
廣義上的可重入鎖,而不是單指JAVA下的ReentrantLock。
可重入鎖,也叫做遞歸鎖,指的是同一線程外層函數獲得鎖之後,内層遞歸函數仍然有擷取該鎖的代碼,但不受影響。
這句話神馬意思?
這種鎖是可以反複進入的。
當一個線程執行到某個synchronized方法時,比如說method1,而在method1中會調用另外一個synchronized方法method2,此時線程不必重新去申請鎖,而是可以直接執行方法method2。
class MyClass {
public synchronized void method1() {
enterNextRoom();
}
public synchronized void method2() {
// todo
}
}
兩個方法method1和method2都用synchronized修飾了,假如某一時刻,線程A執行到了method1,此時線程A擷取了這個對象的鎖,而由于method2也是synchronized方法,假如synchronized不具備可重入性,此時線程A需要重新申請鎖。但是這就會造成一個問題,因為線程A已經持有了該對象的鎖,而又在申請擷取該對象的鎖,這樣就會線程A一直等待永遠不會擷取到的鎖。
如果不是可重入鎖的話,method2可能不會被目前線程執行,可能造成死鎖。
可重入鎖最大的作用是避免死鎖。
實作:
- synchronized
- ReentrantLock
ReentrantLock構造函數
提供了是否公平鎖的初始化:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
使用ReentrantLock必須在finally控制塊中進行解鎖操作。
在資源競争不激烈的情形下,性能稍微比synchronized差點點。但是當同步非常激烈的時候,synchronized的性能一下子能下降好幾十倍,而ReentrantLock确還能維持常态。
高并發量情況下使用ReentrantLock。
優點: 可一定程度避免死鎖。
- Semaphore
- AtomicInteger、AtomicLong等
不可重入鎖的栗子
用自旋鎖來模拟,代碼如下:
public class UnreentrantLock {
private AtomicReference<Thread> owner = new AtomicReference<Thread>();
public void lock() {
Thread current = Thread.currentThread();
//這句是很經典的“自旋”文法,AtomicInteger中也有
for (;;) {
if (!owner.compareAndSet(null, current)) {
return;
}
}
}
public void unlock() {
Thread current = Thread.currentThread();
owner.compareAndSet(current, null);
}
}