Java中鎖的類型詳解
按照鎖的特性分類
公平性分類
公平鎖(Fair lock)
公平鎖指多個線程按照申請鎖的順序依次獲取鎖,遵循先到先得的原則,避免線程饑餓現(xiàn)象。在Java中,公平鎖通常通過ReentrantLock或ReentrantReadWriteLock的構(gòu)造函數(shù)指定公平策略實現(xiàn)。
ReentrantLock的公平模式
通過構(gòu)造函數(shù)傳入true啟用公平鎖:
ReentrantLock fairLock = new ReentrantLock(true); // true表示公平鎖
公平鎖會維護一個線程等待隊列,按請求順序分配鎖,但性能略低于非公平鎖。
ReentrantReadWriteLock的公平模式
類似ReentrantLock,通過構(gòu)造函數(shù)指定公平性:
ReentrantReadWriteLock fairReadWriteLock = new ReentrantReadWriteLock(true);
讀寫鎖的公平模式下,讀鎖和寫鎖的分配均遵循請求順序。
Semaphore的公平模式
信號量也可通過構(gòu)造函數(shù)啟用公平策略:
Semaphore fairSemaphore = new Semaphore(permits, true); // true表示公平
公平模式下,線程按申請許可證的順序獲取資源。
注意事項
- 性能權(quán)衡:公平鎖減少線程饑餓但增加上下文切換開銷,非公平鎖吞吐量更高。
- 默認(rèn)行為:
ReentrantLock和Semaphore默認(rèn)是非公平鎖,需顯式聲明公平策略。 - 適用場景:嚴(yán)格順序需求或避免饑餓時使用公平鎖,高并發(fā)場景優(yōu)先考慮非公平鎖。
示例代碼
// 公平鎖示例
ReentrantLock lock = new ReentrantLock(true);
lock.lock();
try {
// 臨界區(qū)代碼
} finally {
lock.unlock();
}非公平鎖(Non-fair-lock)
它不保證線程獲取鎖的順序與請求鎖的順序一致。線程可以在鎖被釋放時直接嘗試獲取鎖,而不考慮是否有其他線程已經(jīng)在等待隊列中。
實現(xiàn)方式
在Java中,ReentrantLock類默認(rèn)使用非公平鎖策略。可以通過以下代碼顯式創(chuàng)建非公平鎖:
ReentrantLock lock = new ReentrantLock(false); // false表示非公平鎖
特點
非公平鎖允許新請求鎖的線程插隊,即使有其他線程在等待隊列中。這種機制減少了線程切換的開銷,提高了吞吐量,但可能導(dǎo)致某些線程長時間無法獲取鎖。
優(yōu)點
- 減少線程切換,提高性能
- 在高并發(fā)場景下吞吐量更高
缺點
- 可能導(dǎo)致某些線程饑餓
- 無法保證公平性
使用場景
- 鎖持有時間較短
- 線程競爭不激烈
- 對吞吐量要求高于公平性要求
與公平鎖的對比
// 非公平鎖 ReentrantLock nonFairLock = new ReentrantLock(false); // 公平鎖 ReentrantLock fairLock = new ReentrantLock(true);
非公平鎖的性能通常優(yōu)于公平鎖,因為減少了線程切換的開銷。但在要求嚴(yán)格的公平性場景下,應(yīng)該使用公平鎖。
非公平鎖的底層實現(xiàn)
非公平鎖通過AQS(AbstractQueuedSynchronizer)實現(xiàn)。當(dāng)線程嘗試獲取鎖時,會先嘗試直接獲取,失敗后才進入等待隊列:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}排他性分類
獨占鎖(Exclusive lock)
獨占鎖(Exclusive Lock)是一種同步機制,同一時間只允許一個線程持有鎖,其他線程必須等待鎖釋放后才能獲取。Java 中主要通過 synchronized 關(guān)鍵字和 ReentrantLock 類實現(xiàn)獨占鎖。
使用synchronized實現(xiàn)獨占鎖
synchronized 是 Java 內(nèi)置的獨占鎖機制,可以修飾方法或代碼塊。
方法級別鎖
public synchronized void exclusiveMethod() {
// 臨界區(qū)代碼
}
代碼塊級別鎖
public void exclusiveBlock() {
synchronized (this) {
// 臨界區(qū)代碼
}
}
特點
- 自動釋放鎖:線程執(zhí)行完同步代碼或發(fā)生異常時,鎖會自動釋放。
- 可重入性:同一線程可重復(fù)獲取已持有的鎖。
使用ReentrantLock實現(xiàn)獨占鎖
ReentrantLock 是 java.util.concurrent.locks 包下的顯式鎖實現(xiàn),提供更靈活的鎖控制。
基本用法
private final ReentrantLock lock = new ReentrantLock();
public void performTask() {
lock.lock(); // 獲取鎖
try {
// 臨界區(qū)代碼
} finally {
lock.unlock(); // 確保鎖釋放
}
}高級功能
可中斷鎖
lock.lockInterruptibly(); // 響應(yīng)中斷的鎖獲取
嘗試獲取鎖
if (lock.tryLock(1, TimeUnit.SECONDS)) { // 嘗試在指定時間內(nèi)獲取鎖
try {
// 臨界區(qū)代碼
} finally {
lock.unlock();
}
}
公平鎖
ReentrantLock fairLock = new ReentrantLock(true); // 公平鎖
應(yīng)用場景
- 資源互斥訪問
如單例模式的雙重檢查鎖、共享變量的線程安全操作。 - 寫操作保護
在讀寫鎖(ReadWriteLock)中,寫鎖是獨占鎖,確保寫操作原子性。
注意事項
- 避免死鎖:確保鎖的獲取和釋放成對出現(xiàn),尤其是異常場景。
- 性能考量:高并發(fā)場景下,
ReentrantLock的靈活性可能優(yōu)于synchronized,但需手動管理鎖釋放。
通過合理選擇 synchronized 或 ReentrantLock,可以高效實現(xiàn)線程安全的獨占訪問控制。
共享鎖 (Shared lock)
共享鎖(Shared Lock)是一種允許多個線程同時讀取資源,但禁止寫入的鎖機制。與排他鎖(Exclusive Lock)互斥的特性不同,共享鎖適用于讀多寫少的場景,能有效提高并發(fā)性能。
Java中主要通過ReadWriteLock接口及其實現(xiàn)類ReentrantReadWriteLock實現(xiàn)共享鎖:
- 讀鎖(共享鎖):通過
readLock()方法獲取,允許多個線程同時持有。 - 寫鎖(排他鎖):通過
writeLock()方法獲取,同一時間僅允許一個線程持有。
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock(); // 共享鎖 ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock(); // 排他鎖
使用場景
- 緩存系統(tǒng):多個線程可并發(fā)讀取緩存數(shù)據(jù),寫入時需獨占。
- 資源池管理:如數(shù)據(jù)庫連接池的讀取操作。
// 示例:使用共享鎖實現(xiàn)線程安全的緩存
class Cache<K, V> {
private final Map<K, V> map = new HashMap<>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public V get(K key) {
rwLock.readLock().lock();
try {
return map.get(key);
} finally {
rwLock.readLock().unlock();
}
}
public void put(K key, V value) {
rwLock.writeLock().lock();
try {
map.put(key, value);
} finally {
rwLock.writeLock().unlock();
}
}
}注意事項
- 鎖升級問題:持有讀鎖時嘗試獲取寫鎖會導(dǎo)致死鎖,需先釋放讀鎖。
- 公平性選擇:
ReentrantReadWriteLock支持公平/非公平模式,非公平模式吞吐量更高。
共享鎖與同步代碼塊的對比
| 特性 | 共享鎖(ReadWriteLock) | synchronized |
|---|---|---|
| 并發(fā)性 | 讀操作并發(fā),寫操作互斥 | 完全互斥 |
| 靈活性 | 可分離讀/寫鎖 | 單一鎖機制 |
其他共享鎖實現(xiàn)
- StampedLock:Java 8引入,支持樂觀讀鎖,適用于讀操作遠(yuǎn)多于寫的場景。
StampedLock stampedLock = new StampedLock();
long stamp = stampedLock.tryOptimisticRead(); // 樂觀讀鎖
if (!stampedLock.validate(stamp)) {
stamp = stampedLock.readLock(); // 退化為悲觀讀鎖
}
獲取方式分類
悲觀鎖(Pessimistic lock)
悲觀鎖是一種并發(fā)控制機制,假設(shè)多線程并發(fā)訪問共享資源時大概率會發(fā)生沖突,因此在訪問數(shù)據(jù)前會先加鎖,確保其他線程無法同時修改。適用于寫操作頻繁的場景。
實現(xiàn)方式
synchronized 關(guān)鍵字
通過synchronized修飾方法或代碼塊,實現(xiàn)隱式鎖:
public synchronized void updateData() {
// 臨界區(qū)代碼
}
或使用代碼塊鎖定特定對象:
public void updateData() {
synchronized (this) { // 鎖住當(dāng)前對象
// 臨界區(qū)代碼
}
}
ReentrantLock
java.util.concurrent.locks.ReentrantLock提供更靈活的顯式鎖:
private final ReentrantLock lock = new ReentrantLock();
public void updateData() {
lock.lock(); // 手動加鎖
try {
// 臨界區(qū)代碼
} finally {
lock.unlock(); // 必須手動釋放
}
}數(shù)據(jù)庫悲觀鎖
在JDBC中可通過SQL語句實現(xiàn):
- SELECT ... FOR UPDATE(MySQL/Oracle):
Connection conn = ...;
try {
conn.setAutoCommit(false);
PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM accounts WHERE id = ? FOR UPDATE"
);
ps.setInt(1, accountId);
ResultSet rs = ps.executeQuery();
// 修改數(shù)據(jù)后提交
conn.commit();
} catch (SQLException e) {
conn.rollback();
}注意事項
- 死鎖風(fēng)險:多個線程互相持有對方所需鎖時會導(dǎo)致死鎖,需設(shè)計合理的加鎖順序。
- 性能開銷:頻繁加鎖可能降低系統(tǒng)吞吐量,讀多寫少的場景建議考慮樂觀鎖。
- 鎖粒度:盡量縮小鎖范圍(如鎖定行而非整表)以減少阻塞。
適用場景
- 數(shù)據(jù)競爭激烈的寫操作。
- 需要保證強一致性的業(yè)務(wù)邏輯(如支付系統(tǒng)扣款)。
樂觀鎖 (Optimistic lock)
樂觀鎖是一種并發(fā)控制機制,假設(shè)多線程操作共享資源時不會發(fā)生沖突,因此在操作前不加鎖,而是在提交更新時檢查資源是否被其他線程修改。如果未被修改,則提交成功;否則,根據(jù)策略(重試、報錯等)處理沖突。樂觀鎖適用于讀多寫少的場景,減少鎖競爭的開銷。
樂觀鎖的實現(xiàn)方式
1. 版本號機制
在數(shù)據(jù)表中增加一個版本號字段(如 version),每次更新時比對版本號。若版本號匹配,則更新數(shù)據(jù)并遞增版本號;否則視為沖突。
示例代碼(基于數(shù)據(jù)庫)
// 假設(shè)有一個實體類
public class Product {
private Long id;
private String name;
private int version; // 樂觀鎖版本號
}
// 更新邏輯
@Transactional
public void updateProduct(Long id, String newName) {
Product product = productDao.selectById(id);
product.setName(newName);
int updated = productDao.updateWithVersion(product);
if (updated == 0) {
throw new OptimisticLockException("更新失敗,數(shù)據(jù)已被修改");
}
}對應(yīng)的 SQL 語句示例:
UPDATE product SET name = #{newName}, version = version + 1
WHERE id = #{id} AND version = #{oldVersion};
2. CAS(Compare-And-Swap)
通過原子操作(如 AtomicInteger)實現(xiàn)樂觀鎖,適用于單機或多線程環(huán)境。
示例代碼(基于 AtomicInteger)
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
int oldValue, newValue;
do {
oldValue = counter.get();
newValue = oldValue + 1;
} while (!counter.compareAndSet(oldValue, newValue));
}優(yōu)點
- 無鎖競爭,提高吞吐量。
- 避免死鎖問題。
缺點
- 沖突頻繁時需重試,可能降低性能。
- 不保證操作原子性,需結(jié)合事務(wù)或其他機制。
適用場景
- 讀多寫少的高并發(fā)場景(如商品庫存、點贊計數(shù))。
- 沖突概率較低的業(yè)務(wù)邏輯。
注意事項
- 版本號需為整型或時間戳,確保可比較性。
- 分布式環(huán)境中需結(jié)合分布式鎖或數(shù)據(jù)庫唯一約束。
狀態(tài)分類
可重入鎖 (Reentrant lock)
可重入鎖(ReentrantLock)是Java中一種顯式鎖機制,屬于java.util.concurrent.locks包。與synchronized關(guān)鍵字相比,它提供更靈活的鎖操作,支持公平鎖、非公平鎖、可中斷鎖等待等特性。
核心特性
- 可重入性
- 同一線程可以多次獲取同一把鎖,避免死鎖。每次獲取鎖后需對應(yīng)釋放,通常通過計數(shù)器實現(xiàn)。
- 公平性選擇
- 通過構(gòu)造函數(shù)指定公平鎖(
fair=true)或非公平鎖(默認(rèn))。公平鎖按請求順序分配,非公平鎖允許插隊。 - 條件變量(Condition)
- 通過
newCondition()創(chuàng)建多個條件隊列,實現(xiàn)精細(xì)化的線程等待/喚醒機制,類似wait()和notify()。
基本用法
ReentrantLock lock = new ReentrantLock(); // 非公平鎖
lock.lock(); // 獲取鎖
try {
// 臨界區(qū)代碼
} finally {
lock.unlock(); // 確保鎖釋放
}
高級功能
1. 嘗試獲取鎖
tryLock():立即返回是否成功獲取鎖。tryLock(long timeout, TimeUnit unit):在指定時間內(nèi)嘗試獲取鎖。
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 操作臨界區(qū)
} finally {
lock.unlock();
}
} else {
// 處理超時邏輯
}
2. 可中斷鎖
lockInterruptibly()允許在等待鎖時響應(yīng)中斷,避免死等。
try {
lock.lockInterruptibly();
// 臨界區(qū)代碼
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢復(fù)中斷狀態(tài)
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
3. 公平鎖示例
ReentrantLock fairLock = new ReentrantLock(true); // 公平鎖
fairLock.lock();
try {
// 公平鎖保護的代碼
} finally {
fairLock.unlock();
}
與synchronized對比
| 特性 | ReentrantLock | synchronized |
|---|---|---|
| 鎖獲取方式 | 顯式調(diào)用lock()/unlock() | 隱式(代碼塊/方法) |
| 公平性 | 支持配置 | 非公平 |
| 可中斷 | 支持 | 不支持 |
| 條件變量 | 支持多個Condition | 單一wait()/notify() |
| 性能 | 高競爭時更優(yōu) | 低競爭時更優(yōu) |
注意事項
- 必須在
finally塊中釋放鎖,避免異常導(dǎo)致鎖泄漏。 - 避免嵌套過多鎖操作,可能導(dǎo)致邏輯復(fù)雜化。
- 公平鎖可能降低吞吐量,需根據(jù)場景權(quán)衡。
通過合理使用ReentrantLock,可以更靈活地控制多線程并發(fā),尤其適用于需要復(fù)雜同步策略的場景。
不可重入鎖 (Non-reentrant lock)
不可重入鎖(Non-Reentrant Lock)是一種線程同步機制,特點是同一線程在持有鎖的情況下,若再次嘗試獲取該鎖,會導(dǎo)致線程阻塞或死鎖。與可重入鎖(如 ReentrantLock)不同,不可重入鎖不記錄持有線程的重復(fù)獲取次數(shù)。
不可重入鎖通常通過以下方式實現(xiàn):
- 鎖狀態(tài)標(biāo)記:使用一個布爾變量(如
isLocked)表示鎖是否被占用。 - 線程檢查:獲取鎖時,若鎖已被占用(無論是否當(dāng)前線程持有),均會阻塞。
以下是一個簡單的不可重入鎖實現(xiàn)示例:
public class NonReentrantLock {
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException {
while (isLocked) {
wait(); // 若鎖被占用,當(dāng)前線程等待
}
isLocked = true; // 獲取鎖
}
public synchronized void unlock() {
isLocked = false;
notify(); // 喚醒等待線程
}
}不可重入鎖的問題
死鎖風(fēng)險:若線程在持有鎖時重復(fù)調(diào)用 lock(),會導(dǎo)致自身阻塞。
NonReentrantLock lock = new NonReentrantLock(); lock.lock(); lock.lock(); // 線程在此處永久阻塞
靈活性不足:無法支持遞歸調(diào)用或嵌套同步代碼塊。
應(yīng)用場景
- 簡單同步需求:僅需基礎(chǔ)互斥且無嵌套鎖的場景。
- 資源限制:明確要求防止同一線程重復(fù)獲取鎖的情況。
不可重入鎖與可重入鎖的對比
| 特性 | 不可重入鎖 | 可重入鎖(如 ReentrantLock) |
|---|---|---|
| 同一線程重復(fù)獲取 | 導(dǎo)致阻塞/死鎖 | 允許,記錄重入次數(shù) |
| 實現(xiàn)復(fù)雜度 | 簡單 | 需維護持有線程和計數(shù)器 |
| 適用場景 | 無嵌套鎖的簡單同步 | 遞歸調(diào)用或復(fù)雜同步邏輯 |
注意事項
- 避免在不可重入鎖保護的代碼中調(diào)用可能再次獲取鎖的方法。
- 若需嵌套鎖,應(yīng)使用
ReentrantLock或synchronized(Java 內(nèi)置的可重入鎖)。
到此這篇關(guān)于Java中鎖的類型詳解的文章就介紹到這了,更多相關(guān)java 鎖類型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis中collection和association的使用區(qū)別詳解
這篇文章主要介紹了Mybatis中collection和association的使用區(qū)別詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11
IntelliJ IDEA 2020.1 EAP4 發(fā)布,重命名/更改簽名新功能一覽
這篇文章主要介紹了IntelliJ IDEA 2020.1 EAP4 發(fā)布,重命名/更改簽名新功能,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04
關(guān)于SpringMVC對Restful風(fēng)格的支持詳解
Restful就是一個資源定位及資源操作的風(fēng)格,不是標(biāo)準(zhǔn)也不是協(xié)議,只是一種風(fēng)格,是對http協(xié)議的詮釋,下面這篇文章主要給大家介紹了關(guān)于SpringMVC對Restful風(fēng)格支持的相關(guān)資料,需要的朋友可以參考下2022-01-01

