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