Java的獨(dú)占鎖和共享鎖,你了解了么?
昨天了不起帶著大家一起學(xué)習(xí)了關(guān)于這個(gè)樂觀鎖,悲觀鎖,遞歸鎖以及讀寫鎖,今天我們再來看看這個(gè)關(guān)于 Java 的其他的鎖,大家都了解 Java 的鎖有很多種,我們今天再來介紹四種鎖。
公平鎖
Java 中的公平鎖是一種多線程同步機(jī)制,它試圖按照線程請求鎖的順序來分配鎖。公平鎖的主要目標(biāo)是避免“線程饑餓”問題,即某些線程長時(shí)間得不到執(zhí)行的情況。
在 Java 的 java.util.concurrent.locks 包中,ReentrantLock 是一個(gè)可重入的互斥鎖,它提供了公平鎖和非公平鎖兩種策略。當(dāng)你創(chuàng)建一個(gè) ReentrantLock 實(shí)例時(shí),可以指定它是否為公平鎖:
// 創(chuàng)建一個(gè)公平鎖
ReentrantLock fairLock = new ReentrantLock(true);
在公平鎖策略中,等待時(shí)間最長的線程將獲得鎖。公平鎖通過維護(hù)一個(gè)隊(duì)列來跟蹤等待鎖的線程,并按照它們進(jìn)入隊(duì)列的順序?yàn)樗鼈兎峙滏i。然而,需要注意的是,公平鎖并不能完全保證公平性,因?yàn)榫€程調(diào)度仍然受到操作系統(tǒng)和 JVM 的影響。
公平鎖的一個(gè)主要缺點(diǎn)是性能。由于需要維護(hù)一個(gè)隊(duì)列來跟蹤等待鎖的線程,并且在線程釋放鎖時(shí)需要喚醒等待隊(duì)列中的下一個(gè)線程,因此公平鎖通常比非公平鎖具有更高的開銷。此外,在高并發(fā)場景下,公平鎖可能會導(dǎo)致更高的上下文切換率,從而降低系統(tǒng)性能。
非公平鎖
其實(shí)我們在看到了上面的公平鎖之后,那么就很容易的去了解這個(gè)非公平鎖,因?yàn)榉枪芥i是與公平鎖相對的一種多線程同步機(jī)制。在非公平鎖策略中,鎖的分配并不保證按照線程請求鎖的順序來進(jìn)行。這意味著,即使有一個(gè)線程已經(jīng)等待了很長時(shí)間,新到來的線程仍然有可能立即獲得鎖。
非公平鎖通常具有更高的吞吐量,因?yàn)樗鼈儨p少了維護(hù)等待隊(duì)列所需的開銷。當(dāng)線程嘗試獲取鎖時(shí),它不必檢查或加入等待隊(duì)列,而是直接嘗試獲取鎖。如果鎖當(dāng)前可用,線程就可以立即獲得鎖并執(zhí)行,而不需要等待其他線程。
在 Java 的 java.util.concurrent.locks 包中,ReentrantLock 類的默認(rèn)構(gòu)造函數(shù)創(chuàng)建的就是一個(gè)非公平鎖:
// 創(chuàng)建一個(gè)非公平鎖
ReentrantLock unfairLock = new ReentrantLock();
非公平鎖的優(yōu)勢在于它們通常能夠更有效地利用系統(tǒng)資源,特別是在高并發(fā)場景下。由于減少了線程間的切換和等待,非公平鎖通常能夠提供更高的性能。
然而,非公平鎖的一個(gè)潛在缺點(diǎn)是它們可能會導(dǎo)致線程饑餓。如果有一個(gè)或多個(gè)線程持續(xù)地被新到來的線程搶占,那么這些等待的線程可能會長時(shí)間得不到執(zhí)行。這種情況在高負(fù)載或資源競爭激烈的系統(tǒng)中尤其可能發(fā)生。
在選擇使用公平鎖還是非公平鎖時(shí),應(yīng)該根據(jù)應(yīng)用程序的具體需求進(jìn)行權(quán)衡。如果系統(tǒng)對公平性有嚴(yán)格要求,或者想要避免線程饑餓問題,那么公平鎖可能是一個(gè)更好的選擇。如果系統(tǒng)更關(guān)注性能,并且可以接受一定程度的不公平性,那么非公平鎖可能更加合適。
共享鎖
在Java中,共享鎖(Shared Lock)是一種允許多個(gè)線程同時(shí)讀取資源,但在寫入資源時(shí)只允許一個(gè)線程獨(dú)占的鎖。這種鎖通常用于提高讀取操作的并發(fā)性,因?yàn)樽x取操作通常不會修改數(shù)據(jù),所以允許多個(gè)線程同時(shí)進(jìn)行讀取是安全的。
Java的java.util.concurrent.locks包中的ReentrantReadWriteLock類就是一種實(shí)現(xiàn)了共享鎖和獨(dú)占鎖(排他鎖)機(jī)制的讀寫鎖。在這個(gè)鎖中,讀鎖是共享的,寫鎖是獨(dú)占的。
我們來看看示例代碼:
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
// 讀取數(shù)據(jù)時(shí)獲取讀鎖
readLock.lock();
try {
// 讀取共享資源
} finally {
readLock.unlock();
}
// 修改數(shù)據(jù)時(shí)獲取寫鎖
writeLock.lock();
try {
// 修改共享資源
} finally {
writeLock.unlock();
}
在上面的代碼中,多個(gè)線程可以同時(shí)獲取讀鎖來讀取數(shù)據(jù),但當(dāng)一個(gè)線程獲取了寫鎖時(shí),其他線程既不能獲取讀鎖也不能獲取寫鎖,直到寫鎖被釋放。
ReentrantReadWriteLock有兩種模式:公平模式和非公平模式。在公平模式下,等待時(shí)間最長的線程將優(yōu)先獲得鎖;而在非公平模式下,鎖的分配不保證任何特定的順序,新到來的線程可能立即獲得鎖。
要注意的是,盡管讀鎖是共享的,但寫鎖是獨(dú)占的,并且寫鎖具有更高的優(yōu)先級。這意味著當(dāng)一個(gè)線程持有寫鎖時(shí),其他線程無法獲取讀鎖或?qū)戞i。此外,如果一個(gè)線程正在讀取數(shù)據(jù),并且有其他線程請求寫鎖,那么寫線程將會被阻塞,直到所有讀線程釋放讀鎖。
ReentrantReadWriteLock的讀鎖和寫鎖都是可重入的,這意味著一個(gè)線程可以多次獲取同一個(gè)鎖而不會導(dǎo)致死鎖。
使用共享鎖可以顯著提高讀取密集型應(yīng)用的性能,因?yàn)樗试S多個(gè)讀取線程并發(fā)執(zhí)行,而寫入密集型應(yīng)用可能會因?yàn)閷戞i的競爭而受到限制。
獨(dú)占鎖
在Java中,獨(dú)占鎖(Exclusive Lock)是一種同步機(jī)制,它確保在給定時(shí)間內(nèi)只有一個(gè)線程能夠訪問特定的資源或代碼塊。當(dāng)一個(gè)線程持有獨(dú)占鎖時(shí),其他試圖獲取同一鎖的線程將會被阻塞,直到持有鎖的線程釋放該鎖。
java.util.concurrent.locks包中的ReentrantLock就是一種獨(dú)占鎖(也被稱為排他鎖或互斥鎖)的實(shí)現(xiàn)。此外,synchronized關(guān)鍵字在Java中也被用作實(shí)現(xiàn)獨(dú)占鎖的一種方式。
我們看看獨(dú)占鎖的示例代碼:
import java.util.concurrent.locks.ReentrantLock;
public class ExclusiveLockExample {
private final ReentrantLock lock = new ReentrantLock();
private int sharedData;
public void updateData(int newValue) {
lock.lock(); // 獲取獨(dú)占鎖
try {
// 在此區(qū)域內(nèi)只有一個(gè)線程能夠執(zhí)行
sharedData = newValue;
} finally {
lock.unlock(); // 釋放獨(dú)占鎖
}
}
public int readData() {
lock.lock(); // 獲取獨(dú)占鎖以進(jìn)行讀?。m然通常讀取操作可以使用讀鎖來允許多個(gè)線程并發(fā)讀?。?
try {
// 在此區(qū)域內(nèi)只有一個(gè)線程能夠執(zhí)行
return sharedData;
} finally {
lock.unlock(); // 釋放獨(dú)占鎖
}
}
}
在這個(gè)例子中,updateData和readData方法都使用了獨(dú)占鎖來確保同時(shí)只有一個(gè)線程能夠訪問sharedData變量。
上面這個(gè)示例是使用的ReentrantLock的獨(dú)占鎖,既然我們說了 synchronized 關(guān)鍵字也是可以的,我們看看使用這個(gè) synchronized 關(guān)鍵字的獨(dú)占鎖:
public class SynchronizedExample {
private int sharedData;
public synchronized void updateData(int newValue) {
// 在此區(qū)域內(nèi)只有一個(gè)線程能夠執(zhí)行
sharedData = newValue;
}
public synchronized int readData() {
// 在此區(qū)域內(nèi)只有一個(gè)線程能夠執(zhí)行
return sharedData;
}
}
在synchronized這個(gè)例子中,updateData和readData方法都被聲明為synchronized,這意味著它們在同一時(shí)間內(nèi)只能由一個(gè)線程訪問。synchronized關(guān)鍵字提供了一種簡便的方式來實(shí)現(xiàn)獨(dú)占鎖,而不需要顯式地創(chuàng)建鎖對象。
獨(dú)占鎖對于保護(hù)臨界區(qū)(critical sections)非常有用,臨界區(qū)是一段代碼,它訪問或修改共享資源,并且必須被串行執(zhí)行以防止數(shù)據(jù)不一致。然而,獨(dú)占鎖可能會降低并發(fā)性,因?yàn)樗柚沽硕鄠€(gè)線程同時(shí)訪問被保護(hù)的資源。因此,在設(shè)計(jì)并發(fā)系統(tǒng)時(shí),需要仔細(xì)權(quán)衡獨(dú)占鎖的使用。
所以關(guān)于這四種鎖,你了解了么?