Java中鎖的類型詳解
按照鎖的特性分類
公平性分類
公平鎖(Fair lock)
公平鎖指多個(gè)線程按照申請(qǐng)鎖的順序依次獲取鎖,遵循先到先得的原則,避免線程饑餓現(xiàn)象。在Java中,公平鎖通常通過ReentrantLock或ReentrantReadWriteLock的構(gòu)造函數(shù)指定公平策略實(shí)現(xiàn)。
ReentrantLock的公平模式
通過構(gòu)造函數(shù)傳入true啟用公平鎖:
ReentrantLock fairLock = new ReentrantLock(true); // true表示公平鎖
公平鎖會(huì)維護(hù)一個(gè)線程等待隊(duì)列,按請(qǐng)求順序分配鎖,但性能略低于非公平鎖。
ReentrantReadWriteLock的公平模式
類似ReentrantLock,通過構(gòu)造函數(shù)指定公平性:
ReentrantReadWriteLock fairReadWriteLock = new ReentrantReadWriteLock(true);
讀寫鎖的公平模式下,讀鎖和寫鎖的分配均遵循請(qǐng)求順序。
Semaphore的公平模式
信號(hào)量也可通過構(gòu)造函數(shù)啟用公平策略:
Semaphore fairSemaphore = new Semaphore(permits, true); // true表示公平
公平模式下,線程按申請(qǐng)?jiān)S可證的順序獲取資源。
注意事項(xiàng)
- 性能權(quán)衡:公平鎖減少線程饑餓但增加上下文切換開銷,非公平鎖吞吐量更高。
- 默認(rèn)行為:
ReentrantLock和Semaphore默認(rèn)是非公平鎖,需顯式聲明公平策略。 - 適用場(chǎng)景:嚴(yán)格順序需求或避免饑餓時(shí)使用公平鎖,高并發(fā)場(chǎng)景優(yōu)先考慮非公平鎖。
示例代碼
// 公平鎖示例
ReentrantLock lock = new ReentrantLock(true);
lock.lock();
try {
// 臨界區(qū)代碼
} finally {
lock.unlock();
}非公平鎖(Non-fair-lock)
它不保證線程獲取鎖的順序與請(qǐng)求鎖的順序一致。線程可以在鎖被釋放時(shí)直接嘗試獲取鎖,而不考慮是否有其他線程已經(jīng)在等待隊(duì)列中。
實(shí)現(xiàn)方式
在Java中,ReentrantLock類默認(rèn)使用非公平鎖策略??梢酝ㄟ^以下代碼顯式創(chuàng)建非公平鎖:
ReentrantLock lock = new ReentrantLock(false); // false表示非公平鎖
特點(diǎn)
非公平鎖允許新請(qǐng)求鎖的線程插隊(duì),即使有其他線程在等待隊(duì)列中。這種機(jī)制減少了線程切換的開銷,提高了吞吐量,但可能導(dǎo)致某些線程長(zhǎng)時(shí)間無法獲取鎖。
優(yōu)點(diǎn)
- 減少線程切換,提高性能
- 在高并發(fā)場(chǎng)景下吞吐量更高
缺點(diǎn)
- 可能導(dǎo)致某些線程饑餓
- 無法保證公平性
使用場(chǎng)景
- 鎖持有時(shí)間較短
- 線程競(jìng)爭(zhēng)不激烈
- 對(duì)吞吐量要求高于公平性要求
與公平鎖的對(duì)比
// 非公平鎖 ReentrantLock nonFairLock = new ReentrantLock(false); // 公平鎖 ReentrantLock fairLock = new ReentrantLock(true);
非公平鎖的性能通常優(yōu)于公平鎖,因?yàn)闇p少了線程切換的開銷。但在要求嚴(yán)格的公平性場(chǎng)景下,應(yīng)該使用公平鎖。
非公平鎖的底層實(shí)現(xiàn)
非公平鎖通過AQS(AbstractQueuedSynchronizer)實(shí)現(xiàn)。當(dāng)線程嘗試獲取鎖時(shí),會(huì)先嘗試直接獲取,失敗后才進(jìn)入等待隊(duì)列:
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;
}排他性分類
獨(dú)占鎖(Exclusive lock)
獨(dú)占鎖(Exclusive Lock)是一種同步機(jī)制,同一時(shí)間只允許一個(gè)線程持有鎖,其他線程必須等待鎖釋放后才能獲取。Java 中主要通過 synchronized 關(guān)鍵字和 ReentrantLock 類實(shí)現(xiàn)獨(dú)占鎖。
使用synchronized實(shí)現(xiàn)獨(dú)占鎖
synchronized 是 Java 內(nèi)置的獨(dú)占鎖機(jī)制,可以修飾方法或代碼塊。
方法級(jí)別鎖
public synchronized void exclusiveMethod() {
// 臨界區(qū)代碼
}
代碼塊級(jí)別鎖
public void exclusiveBlock() {
synchronized (this) {
// 臨界區(qū)代碼
}
}
特點(diǎn)
- 自動(dòng)釋放鎖:線程執(zhí)行完同步代碼或發(fā)生異常時(shí),鎖會(huì)自動(dòng)釋放。
- 可重入性:同一線程可重復(fù)獲取已持有的鎖。
使用ReentrantLock實(shí)現(xiàn)獨(dú)占鎖
ReentrantLock 是 java.util.concurrent.locks 包下的顯式鎖實(shí)現(xiàn),提供更靈活的鎖控制。
基本用法
private final ReentrantLock lock = new ReentrantLock();
public void performTask() {
lock.lock(); // 獲取鎖
try {
// 臨界區(qū)代碼
} finally {
lock.unlock(); // 確保鎖釋放
}
}高級(jí)功能
可中斷鎖
lock.lockInterruptibly(); // 響應(yīng)中斷的鎖獲取
嘗試獲取鎖
if (lock.tryLock(1, TimeUnit.SECONDS)) { // 嘗試在指定時(shí)間內(nèi)獲取鎖
try {
// 臨界區(qū)代碼
} finally {
lock.unlock();
}
}
公平鎖
ReentrantLock fairLock = new ReentrantLock(true); // 公平鎖
應(yīng)用場(chǎng)景
- 資源互斥訪問
如單例模式的雙重檢查鎖、共享變量的線程安全操作。 - 寫操作保護(hù)
在讀寫鎖(ReadWriteLock)中,寫鎖是獨(dú)占鎖,確保寫操作原子性。
注意事項(xiàng)
- 避免死鎖:確保鎖的獲取和釋放成對(duì)出現(xiàn),尤其是異常場(chǎng)景。
- 性能考量:高并發(fā)場(chǎng)景下,
ReentrantLock的靈活性可能優(yōu)于synchronized,但需手動(dòng)管理鎖釋放。
通過合理選擇 synchronized 或 ReentrantLock,可以高效實(shí)現(xiàn)線程安全的獨(dú)占訪問控制。
共享鎖 (Shared lock)
共享鎖(Shared Lock)是一種允許多個(gè)線程同時(shí)讀取資源,但禁止寫入的鎖機(jī)制。與排他鎖(Exclusive Lock)互斥的特性不同,共享鎖適用于讀多寫少的場(chǎng)景,能有效提高并發(fā)性能。
Java中主要通過ReadWriteLock接口及其實(shí)現(xiàn)類ReentrantReadWriteLock實(shí)現(xiàn)共享鎖:
- 讀鎖(共享鎖):通過
readLock()方法獲取,允許多個(gè)線程同時(shí)持有。 - 寫鎖(排他鎖):通過
writeLock()方法獲取,同一時(shí)間僅允許一個(gè)線程持有。
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock(); // 共享鎖 ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock(); // 排他鎖
使用場(chǎng)景
- 緩存系統(tǒng):多個(gè)線程可并發(fā)讀取緩存數(shù)據(jù),寫入時(shí)需獨(dú)占。
- 資源池管理:如數(shù)據(jù)庫連接池的讀取操作。
// 示例:使用共享鎖實(shí)現(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();
}
}
}注意事項(xiàng)
- 鎖升級(jí)問題:持有讀鎖時(shí)嘗試獲取寫鎖會(huì)導(dǎo)致死鎖,需先釋放讀鎖。
- 公平性選擇:
ReentrantReadWriteLock支持公平/非公平模式,非公平模式吞吐量更高。
共享鎖與同步代碼塊的對(duì)比
| 特性 | 共享鎖(ReadWriteLock) | synchronized |
|---|---|---|
| 并發(fā)性 | 讀操作并發(fā),寫操作互斥 | 完全互斥 |
| 靈活性 | 可分離讀/寫鎖 | 單一鎖機(jī)制 |
其他共享鎖實(shí)現(xiàn)
- StampedLock:Java 8引入,支持樂觀讀鎖,適用于讀操作遠(yuǎn)多于寫的場(chǎng)景。
StampedLock stampedLock = new StampedLock();
long stamp = stampedLock.tryOptimisticRead(); // 樂觀讀鎖
if (!stampedLock.validate(stamp)) {
stamp = stampedLock.readLock(); // 退化為悲觀讀鎖
}
獲取方式分類
悲觀鎖(Pessimistic lock)
悲觀鎖是一種并發(fā)控制機(jī)制,假設(shè)多線程并發(fā)訪問共享資源時(shí)大概率會(huì)發(fā)生沖突,因此在訪問數(shù)據(jù)前會(huì)先加鎖,確保其他線程無法同時(shí)修改。適用于寫操作頻繁的場(chǎng)景。
實(shí)現(xiàn)方式
synchronized 關(guān)鍵字
通過synchronized修飾方法或代碼塊,實(shí)現(xiàn)隱式鎖:
public synchronized void updateData() {
// 臨界區(qū)代碼
}
或使用代碼塊鎖定特定對(duì)象:
public void updateData() {
synchronized (this) { // 鎖住當(dāng)前對(duì)象
// 臨界區(qū)代碼
}
}
ReentrantLock
java.util.concurrent.locks.ReentrantLock提供更靈活的顯式鎖:
private final ReentrantLock lock = new ReentrantLock();
public void updateData() {
lock.lock(); // 手動(dòng)加鎖
try {
// 臨界區(qū)代碼
} finally {
lock.unlock(); // 必須手動(dòng)釋放
}
}數(shù)據(jù)庫悲觀鎖
在JDBC中可通過SQL語句實(shí)現(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();
}注意事項(xiàng)
- 死鎖風(fēng)險(xiǎn):多個(gè)線程互相持有對(duì)方所需鎖時(shí)會(huì)導(dǎo)致死鎖,需設(shè)計(jì)合理的加鎖順序。
- 性能開銷:頻繁加鎖可能降低系統(tǒng)吞吐量,讀多寫少的場(chǎng)景建議考慮樂觀鎖。
- 鎖粒度:盡量縮小鎖范圍(如鎖定行而非整表)以減少阻塞。
適用場(chǎng)景
- 數(shù)據(jù)競(jìng)爭(zhēng)激烈的寫操作。
- 需要保證強(qiáng)一致性的業(yè)務(wù)邏輯(如支付系統(tǒng)扣款)。
樂觀鎖 (Optimistic lock)
樂觀鎖是一種并發(fā)控制機(jī)制,假設(shè)多線程操作共享資源時(shí)不會(huì)發(fā)生沖突,因此在操作前不加鎖,而是在提交更新時(shí)檢查資源是否被其他線程修改。如果未被修改,則提交成功;否則,根據(jù)策略(重試、報(bào)錯(cuò)等)處理沖突。樂觀鎖適用于讀多寫少的場(chǎng)景,減少鎖競(jìng)爭(zhēng)的開銷。
樂觀鎖的實(shí)現(xiàn)方式
1. 版本號(hào)機(jī)制
在數(shù)據(jù)表中增加一個(gè)版本號(hào)字段(如 version),每次更新時(shí)比對(duì)版本號(hào)。若版本號(hào)匹配,則更新數(shù)據(jù)并遞增版本號(hào);否則視為沖突。
示例代碼(基于數(shù)據(jù)庫)
// 假設(shè)有一個(gè)實(shí)體類
public class Product {
private Long id;
private String name;
private int version; // 樂觀鎖版本號(hào)
}
// 更新邏輯
@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ù)已被修改");
}
}對(duì)應(yīng)的 SQL 語句示例:
UPDATE product SET name = #{newName}, version = version + 1
WHERE id = #{id} AND version = #{oldVersion};
2. CAS(Compare-And-Swap)
通過原子操作(如 AtomicInteger)實(shí)現(xiàn)樂觀鎖,適用于單機(jī)或多線程環(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)點(diǎn)
- 無鎖競(jìng)爭(zhēng),提高吞吐量。
- 避免死鎖問題。
缺點(diǎn)
- 沖突頻繁時(shí)需重試,可能降低性能。
- 不保證操作原子性,需結(jié)合事務(wù)或其他機(jī)制。
適用場(chǎng)景
- 讀多寫少的高并發(fā)場(chǎng)景(如商品庫存、點(diǎn)贊計(jì)數(shù))。
- 沖突概率較低的業(yè)務(wù)邏輯。
注意事項(xiàng)
- 版本號(hào)需為整型或時(shí)間戳,確??杀容^性。
- 分布式環(huán)境中需結(jié)合分布式鎖或數(shù)據(jù)庫唯一約束。
狀態(tài)分類
可重入鎖 (Reentrant lock)
可重入鎖(ReentrantLock)是Java中一種顯式鎖機(jī)制,屬于java.util.concurrent.locks包。與synchronized關(guān)鍵字相比,它提供更靈活的鎖操作,支持公平鎖、非公平鎖、可中斷鎖等待等特性。
核心特性
- 可重入性
- 同一線程可以多次獲取同一把鎖,避免死鎖。每次獲取鎖后需對(duì)應(yīng)釋放,通常通過計(jì)數(shù)器實(shí)現(xiàn)。
- 公平性選擇
- 通過構(gòu)造函數(shù)指定公平鎖(
fair=true)或非公平鎖(默認(rèn))。公平鎖按請(qǐng)求順序分配,非公平鎖允許插隊(duì)。 - 條件變量(Condition)
- 通過
newCondition()創(chuàng)建多個(gè)條件隊(duì)列,實(shí)現(xiàn)精細(xì)化的線程等待/喚醒機(jī)制,類似wait()和notify()。
基本用法
ReentrantLock lock = new ReentrantLock(); // 非公平鎖
lock.lock(); // 獲取鎖
try {
// 臨界區(qū)代碼
} finally {
lock.unlock(); // 確保鎖釋放
}
高級(jí)功能
1. 嘗試獲取鎖
tryLock():立即返回是否成功獲取鎖。tryLock(long timeout, TimeUnit unit):在指定時(shí)間內(nèi)嘗試獲取鎖。
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 操作臨界區(qū)
} finally {
lock.unlock();
}
} else {
// 處理超時(shí)邏輯
}
2. 可中斷鎖
lockInterruptibly()允許在等待鎖時(shí)響應(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 {
// 公平鎖保護(hù)的代碼
} finally {
fairLock.unlock();
}
與synchronized對(duì)比
| 特性 | ReentrantLock | synchronized |
|---|---|---|
| 鎖獲取方式 | 顯式調(diào)用lock()/unlock() | 隱式(代碼塊/方法) |
| 公平性 | 支持配置 | 非公平 |
| 可中斷 | 支持 | 不支持 |
| 條件變量 | 支持多個(gè)Condition | 單一wait()/notify() |
| 性能 | 高競(jìng)爭(zhēng)時(shí)更優(yōu) | 低競(jìng)爭(zhēng)時(shí)更優(yōu) |
注意事項(xiàng)
- 必須在
finally塊中釋放鎖,避免異常導(dǎo)致鎖泄漏。 - 避免嵌套過多鎖操作,可能導(dǎo)致邏輯復(fù)雜化。
- 公平鎖可能降低吞吐量,需根據(jù)場(chǎng)景權(quán)衡。
通過合理使用ReentrantLock,可以更靈活地控制多線程并發(fā),尤其適用于需要復(fù)雜同步策略的場(chǎng)景。
不可重入鎖 (Non-reentrant lock)
不可重入鎖(Non-Reentrant Lock)是一種線程同步機(jī)制,特點(diǎn)是同一線程在持有鎖的情況下,若再次嘗試獲取該鎖,會(huì)導(dǎo)致線程阻塞或死鎖。與可重入鎖(如 ReentrantLock)不同,不可重入鎖不記錄持有線程的重復(fù)獲取次數(shù)。
不可重入鎖通常通過以下方式實(shí)現(xiàn):
- 鎖狀態(tài)標(biāo)記:使用一個(gè)布爾變量(如
isLocked)表示鎖是否被占用。 - 線程檢查:獲取鎖時(shí),若鎖已被占用(無論是否當(dāng)前線程持有),均會(huì)阻塞。
以下是一個(gè)簡(jiǎn)單的不可重入鎖實(shí)現(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)險(xiǎn):若線程在持有鎖時(shí)重復(fù)調(diào)用 lock(),會(huì)導(dǎo)致自身阻塞。
NonReentrantLock lock = new NonReentrantLock(); lock.lock(); lock.lock(); // 線程在此處永久阻塞
靈活性不足:無法支持遞歸調(diào)用或嵌套同步代碼塊。
應(yīng)用場(chǎng)景
- 簡(jiǎn)單同步需求:僅需基礎(chǔ)互斥且無嵌套鎖的場(chǎng)景。
- 資源限制:明確要求防止同一線程重復(fù)獲取鎖的情況。
不可重入鎖與可重入鎖的對(duì)比
| 特性 | 不可重入鎖 | 可重入鎖(如 ReentrantLock) |
|---|---|---|
| 同一線程重復(fù)獲取 | 導(dǎo)致阻塞/死鎖 | 允許,記錄重入次數(shù) |
| 實(shí)現(xiàn)復(fù)雜度 | 簡(jiǎn)單 | 需維護(hù)持有線程和計(jì)數(shù)器 |
| 適用場(chǎng)景 | 無嵌套鎖的簡(jiǎn)單同步 | 遞歸調(diào)用或復(fù)雜同步邏輯 |
注意事項(xiàng)
- 避免在不可重入鎖保護(hù)的代碼中調(diào)用可能再次獲取鎖的方法。
- 若需嵌套鎖,應(yīng)使用
ReentrantLock或synchronized(Java 內(nèi)置的可重入鎖)。
到此這篇關(guān)于Java中鎖的類型詳解的文章就介紹到這了,更多相關(guān)java 鎖類型內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring中的FactoryBean與BeanFactory詳細(xì)解析
這篇文章主要介紹了Spring中的FactoryBean與BeanFactory詳細(xì)解析,在Spring框架中,FactoryBean和BeanFactory是兩個(gè)關(guān)鍵的接口,用于創(chuàng)建和管理對(duì)象實(shí)例,它們?cè)赟pring的IoC(Inversion of Control,控制反轉(zhuǎn))容器中發(fā)揮著重要的作用,需要的朋友可以參考下2023-11-11
Spring框架學(xué)習(xí)筆記之方法注解@Bean的使用
這篇文章主要給大家介紹了關(guān)于Spring框架學(xué)習(xí)筆記之方法注解@Bean使用的相關(guān)資料,這是一個(gè)我們很常用的注解,作用是指示一個(gè)方法生成一個(gè)由Spring管理的Bean,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12
微服務(wù)Redis-Session共享登錄狀態(tài)的過程詳解
這篇文章主要介紹了微服務(wù)Redis-Session共享登錄狀態(tài),本文采取Spring security做登錄校驗(yàn),用redis做session共享,實(shí)現(xiàn)單服務(wù)登錄可靠性,微服務(wù)之間調(diào)用的可靠性與通用性,需要的朋友可以參考下2023-12-12
Java servlet執(zhí)行流程代碼實(shí)例
這篇文章主要介紹了Java servlet執(zhí)行流程代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
SpringBoot項(xiàng)目報(bào)錯(cuò):"Error?starting?ApplicationContext....
這篇文章主要給大家介紹了關(guān)于SpringBoot項(xiàng)目報(bào)錯(cuò):“Error?starting?ApplicationContext.?To?display?the?conditions?report?re-run?...”的解決辦法,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
Java解析JSON數(shù)據(jù)時(shí)報(bào)錯(cuò)問題解決方案
這篇文章主要介紹了Java解析JSON數(shù)據(jù)時(shí)報(bào)錯(cuò)問題解決方案,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10

