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

