Java使用重入鎖實(shí)現(xiàn)線程同步的示例代碼
一、項(xiàng)目背景詳細(xì)介紹
在多線程環(huán)境中,為保證對共享資源的安全訪問,常用的同步手段是 synchronized 關(guān)鍵字。但 synchronized 存在以下局限:
- 靈活性較低:不能嘗試超時獲取鎖,也無法中斷獲取鎖的線程;
- 可見性:無法查看當(dāng)前鎖是否被占用或等待隊(duì)列情況;
- 公平性控制:無法直接控制鎖的公平或非公平策略。
Java 5 引入了更強(qiáng)大的 java.util.concurrent.locks 包,其中的 ReentrantLock(可重入鎖)在功能和性能上均優(yōu)于內(nèi)置鎖。它提供:
- 嘗試獲取鎖:
tryLock()、tryLock(timeout, unit) - 可中斷的鎖獲取:
lockInterruptibly() - 公平鎖:通過構(gòu)造函數(shù)選擇公平或非公平策略
- 監(jiān)視器查詢:
getHoldCount()、isLocked()、getQueueLength()等方法
本項(xiàng)目旨在通過示例演示如何使用 ReentrantLock 實(shí)現(xiàn)線程同步,替代 synchronized,并展示其高級功能,如超時嘗試獲取鎖、公平策略和中斷響應(yīng)。
二、項(xiàng)目需求詳細(xì)介紹
基本互斥訪問
- 使用
ReentrantLock替代synchronized,在多個線程間安全地更新同一共享變量或數(shù)據(jù)結(jié)構(gòu); - 提供示例:多線程對同一計數(shù)器或共享列表進(jìn)行增刪操作。
超時獲取鎖
- 演示
tryLock(long timeout, TimeUnit unit)用法,當(dāng)鎖長時間被占用時拋出或走備用邏輯;
可中斷鎖獲取
- 演示
lockInterruptibly(),在等待鎖期間響應(yīng)中斷,避免因鎖阻塞導(dǎo)致的無法取消;
公平鎖與非公平鎖
- 對比默認(rèn)(非公平)鎖和通過
new ReentrantLock(true)創(chuàng)建的公平鎖在高并發(fā)場景下的性能及線程調(diào)度差異;
鎖狀態(tài)監(jiān)控
- 使用
getHoldCount()、getQueueLength()、hasQueuedThreads()等方法,實(shí)時查詢鎖的占用與等待情況,打印日志監(jiān)控;
示例應(yīng)用
- 實(shí)現(xiàn)一個帶超時和中斷功能的共享資源訪問類
SharedResource; - 編寫多線程測試,模擬高并發(fā)下的鎖獲取、超時回退和中斷場景;
配置可控
- 通過構(gòu)造參數(shù)或配置文件動態(tài)切換公平性、超時閾值等;
文檔與示例
- 在 README 中給出代碼調(diào)用示例及注意事項(xiàng);
- 對比
synchronized與ReentrantLock的使用差異。
三、相關(guān)技術(shù)詳細(xì)介紹
java.util.concurrent.locks.ReentrantLock
- 基本方法:
lock()、unlock()、lockInterruptibly()、tryLock()、tryLock(timeout, unit); - 構(gòu)造參數(shù):
new ReentrantLock()(非公平鎖)、new ReentrantLock(true)(公平鎖);
Condition 接口
- 通過
lock.newCondition()創(chuàng)建條件變量,替代wait/notify,支持多條件隊(duì)列; - 方法:
await()、signal()、signalAll();
鎖監(jiān)控與診斷
getHoldCount():返回當(dāng)前線程重入次數(shù);isLocked():鎖是否被任意線程占用;hasQueuedThreads()、getQueueLength():等待鎖的線程信息;
中斷與超時
lockInterruptibly()在鎖等待時可響應(yīng)中斷;tryLock(timeout, unit)在指定時長內(nèi)等待,超時后返回false;
多線程測試
- 使用
ExecutorService啟動多個線程; - 使用
CountDownLatch或CyclicBarrier協(xié)調(diào)線程啟動同步測試; - 記錄鎖獲取次數(shù)與失敗次數(shù)進(jìn)行統(tǒng)計。
四、實(shí)現(xiàn)思路詳細(xì)介紹
SharedResource 類設(shè)計
- 內(nèi)部包含
private final ReentrantLock lock;和可選的Condition; - 提供方法:
void safeIncrement() { lock.lock(); try { /* 更新共享計數(shù) */ } finally { lock.unlock(); } }
boolean trySafeIncrement(long timeout, TimeUnit unit) { if (lock.tryLock(timeout, unit)) { try { ... } finally { lock.unlock(); } } else { /* 超時邏輯 */ } }
void interruptibleAccess() throws InterruptedException { lock.lockInterruptibly(); try { ... } finally { lock.unlock(); } }
- 若需要多個條件,可創(chuàng)建
Condition notEmpty、notFull并在方法中配合使用。
公平與非公平鎖對比
- 在測試中構(gòu)造兩種
SharedResource,公平鎖與非公平鎖,分別運(yùn)行相同并發(fā)測試,比較吞吐量與線程饑餓情況。
監(jiān)控鎖狀態(tài)
- 在方法中或監(jiān)控線程里定期調(diào)用
lock.getQueueLength()、lock.hasQueuedThreads()并打印,觀察等待線程數(shù); - 結(jié)合
lock.getHoldCount()了解重入深度。
多線程測試
- 使用
ExecutorService和多個工作線程不斷調(diào)用不同模式的方法; - 使用
CountDownLatch保證開始同步,使用AtomicInteger統(tǒng)計成功與超時/中斷次數(shù);
文檔示例
- 在 README 中說明各模式使用場景和注意事項(xiàng),例如必須在
finally塊中unlock(),避免死鎖。
/*
* =====================================================
* File: SharedResource.java
* 共享資源類,使用 ReentrantLock 實(shí)現(xiàn)多種同步策略
* =====================================================
*/
package com.example.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class SharedResource {
private int counter = 0;
private final ReentrantLock lock;
private final Condition notZero;
/**
* 構(gòu)造函數(shù):可指定是否公平鎖
*/
public SharedResource(boolean fair) {
this.lock = new ReentrantLock(fair);
this.notZero = lock.newCondition();
}
/**
* 基本互斥:安全地遞增 counter
*/
public void safeIncrement() {
lock.lock();
try {
counter++;
System.out.printf("%s incremented to %d%n",
Thread.currentThread().getName(), counter);
notZero.signalAll();
} finally {
lock.unlock();
}
}
/**
* 帶超時嘗試獲取鎖的遞增
*/
public boolean trySafeIncrement(long timeout, TimeUnit unit) {
boolean acquired = false;
try {
acquired = lock.tryLock(timeout, unit);
if (acquired) {
counter++;
System.out.printf("%s timed increment to %d%n",
Thread.currentThread().getName(), counter);
notZero.signalAll();
return true;
} else {
System.out.printf("%s failed to acquire lock in %d %s%n",
Thread.currentThread().getName(), timeout, unit);
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.printf("%s interrupted while waiting%n",
Thread.currentThread().getName());
return false;
} finally {
if (acquired) lock.unlock();
}
}
/**
* 可中斷地獲取鎖并等待 counter > 0 后消費(fèi)
*/
public int interruptibleConsume() throws InterruptedException {
lock.lockInterruptibly();
try {
while (counter == 0) {
System.out.printf("%s waiting for counter > 0%n",
Thread.currentThread().getName());
notZero.await();
}
counter--;
System.out.printf("%s consumed to %d%n",
Thread.currentThread().getName(), counter);
return counter;
} finally {
lock.unlock();
}
}
/**
* 監(jiān)控方法:打印當(dāng)前鎖狀態(tài)
*/
public void printLockStatus() {
System.out.printf("Lock held by thread: %s, holdCount=%d, queuedThreads=%d%n",
lock.isLocked() && lock.isHeldByCurrentThread()
? Thread.currentThread().getName()
: "other",
lock.getHoldCount(),
lock.getQueueLength());
}
}
/*
* =====================================================
* File: LockDemo.java
* 演示:多線程調(diào)用 SharedResource 不同方法
* =====================================================
*/
package com.example.lock;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class LockDemo {
public static void main(String[] args) throws InterruptedException {
SharedResource fairResource = new SharedResource(true);
SharedResource unfairResource = new SharedResource(false);
ExecutorService exec = Executors.newFixedThreadPool(6);
CountDownLatch startLatch = new CountDownLatch(1);
AtomicInteger successCount = new AtomicInteger(0);
AtomicInteger failCount = new AtomicInteger(0);
// 創(chuàng)建 2 個常規(guī)增量任務(wù)
for (int i = 0; i < 2; i++) {
exec.submit(() -> {
await(startLatch);
fairResource.safeIncrement();
});
}
// 創(chuàng)建 2 個帶超時嘗試鎖任務(wù)
for (int i = 0; i < 2; i++) {
exec.submit(() -> {
await(startLatch);
if (fairResource.trySafeIncrement(500, TimeUnit.MILLISECONDS)) {
successCount.incrementAndGet();
} else {
failCount.incrementAndGet();
}
});
}
// 創(chuàng)建 2 個可中斷消費(fèi)任務(wù)
for (int i = 0; i < 2; i++) {
exec.submit(() -> {
await(startLatch);
try {
unfairResource.interruptibleConsume();
} catch (InterruptedException e) {
System.out.printf("%s interrupted%n",
Thread.currentThread().getName());
}
});
}
// 啟動所有任務(wù)
startLatch.countDown();
// 等待一段時間后中斷消費(fèi)任務(wù)
Thread.sleep(1000);
exec.shutdownNow();
exec.awaitTermination(5, TimeUnit.SECONDS);
System.out.printf("TryLock successes: %d, failures: %d%n",
successCount.get(), failCount.get());
}
private static void await(CountDownLatch latch) {
try {
latch.await();
} catch (InterruptedException ignored) {}
}
}
代碼詳細(xì)解讀
SharedResource
safeIncrement():使用lock.lock()/unlock()實(shí)現(xiàn)基本互斥,并在notZero條件上喚醒等待的消費(fèi)者。trySafeIncrement(timeout, unit):使用tryLock(timeout, unit)帶超時嘗試獲取鎖,超時后返回失敗邏輯。interruptibleConsume():使用lock.lockInterruptibly(),在等待中可響應(yīng)中斷,配合notZero.await()等待條件。printLockStatus():演示查詢鎖狀態(tài)的方法,包括持有計數(shù)和等待隊(duì)列長度。
LockDemo
使用 ExecutorService 啟動 6 個線程:
- 2 個調(diào)用
safeIncrement(); - 2 個調(diào)用
trySafeIncrement(500ms),統(tǒng)計成功與失敗次數(shù); - 2 個調(diào)用
interruptibleConsume(),并在主線程中斷它們,演示可中斷鎖獲??; - 使用
CountDownLatch保證所有線程同時開始。 - 程序運(yùn)行 1 秒后調(diào)用
shutdownNow()中斷消費(fèi)任務(wù),并打印tryLock的統(tǒng)計結(jié)果。
項(xiàng)目詳細(xì)總結(jié)
本示例通過 ReentrantLock 展示了:
- 基本互斥:與
synchronized類似,但可以更靈活地控制鎖釋放時機(jī)。 - 超時獲取鎖:
tryLock(timeout, unit)避免長時間阻塞,方便實(shí)現(xiàn)備用邏輯。 - 可中斷鎖:
lockInterruptibly()在等待鎖時響應(yīng)中斷,提高了取消能力。 - 公平與非公平:可通過構(gòu)造函數(shù)選擇公平策略,避免線程饑餓。
- 鎖監(jiān)控:
getHoldCount()和getQueueLength()等方法便于在運(yùn)行時診斷鎖狀態(tài)。
項(xiàng)目常見問題及解答
為何要在 finally 中 unlock()?
避免在執(zhí)行過程中拋出異常導(dǎo)致鎖未釋放,進(jìn)而引發(fā)死鎖。
tryLock 獲不到鎖后還能重試嗎?
可以在代碼中判斷失敗后循環(huán)調(diào)用,或結(jié)合退避機(jī)制重試。
公平鎖性能更差嗎?
是的,公平鎖會增加上下文切換成本,一般在需要嚴(yán)格順序時使用,否則推薦默認(rèn)非公平鎖。
lockInterruptibly 如何正確處理中斷?
調(diào)用方法需聲明 throws InterruptedException,在捕獲后可執(zhí)行清理邏輯或直接結(jié)束任務(wù)。
如何監(jiān)控生產(chǎn)環(huán)境中的鎖競爭?
利用 lock.getQueueLength() 和日志定期采集,或結(jié)合 APM 工具監(jiān)控線程等待情況。
擴(kuò)展方向與性能優(yōu)化
Condition 多隊(duì)列
使用多個 Condition 實(shí)現(xiàn)更精細(xì)的等待/喚醒控制,例如生產(chǎn)者—消費(fèi)者的 notFull / notEmpty。
鎖分段
對大數(shù)據(jù)結(jié)構(gòu)進(jìn)行分段加鎖(類似 ConcurrentHashMap),降低鎖粒度提升并發(fā)度。
公平性調(diào)優(yōu)
在高并發(fā)場景下考慮非公平鎖與超時重試結(jié)合,避免嚴(yán)格公平帶來的吞吐下降。
鎖剝離
當(dāng)只有讀操作時,可使用 ReadWriteLock 切換到無阻塞讀鎖,提高并發(fā)讀性能。
可視化診斷
集成到監(jiān)控平臺或定制 Web 界面,實(shí)時展示鎖爭用、隊(duì)列長度和線程等待圖。
以上就是Java使用重入鎖實(shí)現(xiàn)線程同步的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Java重入鎖線程同步的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解java爬蟲jsoup解析多空格class數(shù)據(jù)
在本篇內(nèi)容中小編給大家分享了java爬蟲jsoup怎么解析多空格class數(shù)據(jù)的方法和技巧,需要的朋友們跟著學(xué)習(xí)下。2018-12-12
Java實(shí)現(xiàn)圖片轉(zhuǎn)換PDF文件的示例代碼
這篇文章主要介紹了Java實(shí)現(xiàn)圖片轉(zhuǎn)換PDF文件的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
Java字符編碼原理(動力節(jié)點(diǎn)Java學(xué)院整理)
Java開發(fā)中,常常會遇到亂碼的問題,一旦遇到這種問題,常常比較煩惱,大家都不想承認(rèn)是自己的代碼問題,其實(shí)搞明白編碼的本質(zhì)過程就簡單多了,接下來小編給大家?guī)韏ava字符編碼原理,要求看看吧2017-04-04
SpringBoot+Mybatis項(xiàng)目使用Redis做Mybatis的二級緩存的方法
本篇文章主要介紹了SpringBoot+Mybatis項(xiàng)目使用Redis做Mybatis的二級緩存的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
使用IDEA直接連接MySQL數(shù)據(jù)庫的方法
這篇文章主要介紹了如何使用IDEA直接連接MySQL數(shù)據(jù)庫,首先需要新建一個空項(xiàng)目,第一次連接 需要先下載驅(qū)動,文中給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-04-04

