Java并發(fā)編程之Lock鎖機(jī)制從使用到源碼實(shí)現(xiàn)
1. 鎖的基本概念:從現(xiàn)實(shí)世界到代碼世界
1.1 鎖的演進(jìn):synchronized → Lock
想象一下健身房?jī)?chǔ)物柜的使用場(chǎng)景:
- synchronized:像固定密碼鎖 - 簡(jiǎn)單易用但功能有限
- Lock接口:像智能電子鎖 - 功能豐富且靈活可控
// synchronized - 固定密碼鎖
public synchronized void oldMethod() {
// 自動(dòng)上鎖和解鎖
// 但無法中斷、無法超時(shí)、無法嘗試獲取
}
// Lock - 智能電子鎖
public void newMethod() {
Lock lock = new ReentrantLock();
lock.lock(); // 手動(dòng)開鎖
try {
// 臨界區(qū)代碼
} finally {
lock.unlock(); // 手動(dòng)關(guān)鎖
}
}1.2 Lock接口的核心優(yōu)勢(shì)
| 特性 | synchronized | Lock |
|---|---|---|
| 中斷響應(yīng) | ? | ? |
| 超時(shí)控制 | ? | ? |
| 嘗試獲取 | ? | ? |
| 公平性 | ? | ? |
| 條件隊(duì)列 | 單個(gè) | 多個(gè) |
2. AQS:并發(fā)世界的交通指揮中心
2.1 AQS的核心設(shè)計(jì)思想
AQS(AbstractQueuedSynchronizer)就像高速公路收費(fèi)站系統(tǒng):
- state狀態(tài):當(dāng)前可通行的車道數(shù)量
- 同步隊(duì)列:等待通行的車輛排隊(duì)
- CAS操作:智能的車輛調(diào)度系統(tǒng)
/**
* AQS同步狀態(tài)管理示例
*/
public class AQSCoreConcept {
// state字段的三種典型用法:
// 1. 互斥鎖:state = 0(未鎖定) 或 1(已鎖定)
// 2. 重入鎖:state = 重入次數(shù)
// 3. 讀寫鎖:高16位 = 讀鎖計(jì)數(shù),低16位 = 寫鎖計(jì)數(shù)
private volatile int state;
// 三個(gè)核心的state操作方法:
protected final int getState() { return state; }
protected final void setState(int newState) { state = newState; }
protected final boolean compareAndSetState(int expect, int update) {
// CAS原子操作,保證線程安全
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
}2.2 同步隊(duì)列:線程的"等候區(qū)"
/**
* AQS同步隊(duì)列結(jié)構(gòu)演示
*/
public class SyncQueueDemo {
/**
* 同步隊(duì)列節(jié)點(diǎn)結(jié)構(gòu)(雙向鏈表):
*
* head (虛擬節(jié)點(diǎn)) ? [prev|thread|next|waitStatus] ? [prev|thread|next|waitStatus] ? tail
*
* waitStatus狀態(tài)說明:
* - CANCELLED(1):線程已取消
* - SIGNAL(-1):后繼線程需要被喚醒
* - CONDITION(-2):線程在Condition隊(duì)列中
* - PROPAGATE(-3):共享模式下傳播喚醒
*/
// 獨(dú)占模式獲取鎖的典型流程
public void acquireDemo() {
Lock lock = new ReentrantLock();
// 底層調(diào)用AQS的acquire方法
lock.lock(); // -> sync.acquire(1);
/**
* acquire方法執(zhí)行流程:
* 1. tryAcquire()嘗試直接獲取鎖
* 2. 失敗 → addWaiter()加入同步隊(duì)列隊(duì)尾
* 3. acquireQueued()在隊(duì)列中自旋等待
* 4. 被前驅(qū)節(jié)點(diǎn)喚醒后重新嘗試獲取鎖
*/
}
}2.3 自定義鎖實(shí)戰(zhàn):基于AQS實(shí)現(xiàn)TwinsLock
/**
* TwinsLock - 同一時(shí)刻最多允許兩個(gè)線程訪問的共享鎖
* 設(shè)計(jì)思路:將AQS的state作為許可證計(jì)數(shù)器
*/
public class TwinsLock implements Lock {
private final Sync sync = new Sync(2);
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
if (count <= 0) throw new IllegalArgumentException("計(jì)數(shù)必須大于0");
setState(count); // 初始化許可證數(shù)量
}
/**
* 共享模式獲取鎖
* @return 負(fù)數(shù):獲取失?。?:獲取成功但無剩余;正數(shù):獲取成功且有剩余
*/
@Override
public int tryAcquireShared(int reduceCount) {
for (;;) { // 自旋避免CAS失敗
int current = getState();
int newCount = current - reduceCount;
// 如果新計(jì)數(shù)<0(無許可證)或CAS設(shè)置成功,返回結(jié)果
if (newCount < 0 || compareAndSetState(current, newCount)) {
return newCount;
}
}
}
/**
* 共享模式釋放鎖
*/
@Override
public boolean tryReleaseShared(int returnCount) {
for (;;) {
int current = getState();
int newCount = current + returnCount;
if (compareAndSetState(current, newCount)) {
return true;
}
}
}
}
@Override
public void lock() {
sync.acquireShared(1); // 獲取1個(gè)許可證
}
@Override
public void unlock() {
sync.releaseShared(1); // 釋放1個(gè)許可證
}
// 其他Lock方法實(shí)現(xiàn)...
}
/**
* 測(cè)試TwinsLock
*/
public class TwinsLockTest {
@Test
public void testTwinsLock() {
final Lock lock = new TwinsLock();
// 啟動(dòng)10個(gè)線程,但同一時(shí)刻只有2個(gè)能獲取鎖
for (int i = 0; i < 10; i++) {
Thread worker = new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 獲取鎖");
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
});
worker.start();
}
}
}3. 重入鎖ReentrantLock:可重復(fù)使用的智能鎖
3.1 重入性:一把鑰匙開多把鎖
現(xiàn)實(shí)比喻:你進(jìn)了自家大門,還可以用同一把鑰匙打開臥室門、書房門。
/**
* 重入鎖的重入特性演示
*/
public class ReentrantDemo {
private final ReentrantLock lock = new ReentrantLock();
public void outer() {
lock.lock();
try {
System.out.println("外層方法獲取鎖,重入計(jì)數(shù): " + getHoldCount());
inner(); // 重入:同一個(gè)線程再次獲取同一把鎖
} finally {
lock.unlock();
}
}
public void inner() {
lock.lock(); // 這里不會(huì)阻塞,因?yàn)橐呀?jīng)是鎖的持有者
try {
System.out.println("內(nèi)層方法獲取鎖,重入計(jì)數(shù): " + getHoldCount());
} finally {
lock.unlock();
}
}
private int getHoldCount() {
// 返回當(dāng)前線程持有該鎖的次數(shù)
return lock.getHoldCount();
}
}3.2 公平鎖 vs 非公平鎖
公平鎖:像銀行取號(hào)排隊(duì) - 先來先服務(wù)
非公平鎖:像公交車搶座位 - 誰能搶到誰坐
/**
* 公平性對(duì)比測(cè)試
*/
public class FairVsUnfairTest {
private static Lock fairLock = new ReentrantLock(true); // 公平鎖
private static Lock unfairLock = new ReentrantLock(false); // 非公平鎖
@Test
public void comparePerformance() {
// 測(cè)試結(jié)果通常顯示:
// - 公平鎖:保證順序,但性能較低
// - 非公平鎖:可能饑餓,但吞吐量高
testLock("公平鎖", fairLock);
testLock("非公平鎖", unfairLock);
}
private void testLock(String type, Lock lock) {
long start = System.currentTimeMillis();
// 多個(gè)線程競(jìng)爭(zhēng)鎖...
long duration = System.currentTimeMillis() - start;
System.out.println(type + " 耗時(shí): " + duration + "ms");
}
}3.3 重入鎖實(shí)現(xiàn)原理
/**
* 重入鎖核心實(shí)現(xiàn)解析
*/
public class ReentrantLockCore {
/**
* 非公平鎖獲取邏輯
*/
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) throw new Error("超過最大鎖計(jì)數(shù)");
setState(nextc); // 增加重入計(jì)數(shù)
return true;
}
return false;
}
/**
* 釋放鎖邏輯
*/
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { // 完全釋放
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}4. 讀寫鎖ReentrantReadWriteLock:讀寫分離的高并發(fā)鎖
4.1 讀寫鎖的應(yīng)用場(chǎng)景
現(xiàn)實(shí)比喻:圖書館管理規(guī)則
- 讀操作:多人可同時(shí)閱讀同一本書
- 寫操作:修改書籍時(shí)需獨(dú)占訪問
/**
* 基于讀寫鎖的緩存實(shí)現(xiàn)
*/
public class ReadWriteCache<K, V> {
private final Map<K, V> cache = new HashMap<>();
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
/**
* 讀操作:共享鎖,允許多個(gè)線程同時(shí)讀
*/
public V get(K key) {
readLock.lock();
try {
String value = cache.get(key);
// 模擬配置讀取的耗時(shí)操作
simulateProcess(1);
return value;
} finally {
readLock.unlock();
}
}
/**
* 批量獲取配置 - 讀鎖支持并發(fā)
*/
public Map<String, String> getConfigs(Set<String> keys) {
readLock.lock();
try {
Map<String, String> result = new HashMap<>();
for (String key : keys) {
result.put(key, cache.get(key));
}
simulateProcess(keys.size());
return result;
} finally {
readLock.unlock();
}
}
/**
* 更新配置 - 低頻操作,使用寫鎖
*/
public void updateConfig(String key, String value) {
writeLock.lock();
try {
// 模擬配置更新的耗時(shí)操作
simulateProcess(10);
cache.put(key, value);
System.out.println("配置更新: " + key + " = " + value);
} finally {
writeLock.unlock();
}
}
private void simulateProcess(int milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}4.2 讀寫鎖的狀態(tài)設(shè)計(jì)
/**
* 讀寫鎖狀態(tài)設(shè)計(jì)的精妙之處
*/
public class ReadWriteStateDesign {
/**
* 32位state字段的劃分:
*
* ┌─────────────────┬─────────────────┐
* │ 高16位 │ 低16位 │
* │ 讀狀態(tài) │ 寫狀態(tài) │
* │ (讀鎖計(jì)數(shù)) │ (寫鎖重入數(shù)) │
* └─────────────────┴─────────────────┘
*/
// 獲取寫狀態(tài)(低16位)
static int exclusiveCount(int c) {
return c & 0x0000FFFF;
}
// 獲取讀狀態(tài)(高16位)
static int sharedCount(int c) {
return c >>> 16;
}
// 讀鎖計(jì)數(shù)+1
int newReadState = currentState + (1 << 16); // 即 + 0x00010000
// 寫鎖計(jì)數(shù)+1
int newWriteState = currentState + 1;
}4.3 鎖降級(jí):保證數(shù)據(jù)可見性的重要技術(shù)
/**
* 鎖降級(jí)示例:寫鎖 → 讀鎖
* 目的:保證數(shù)據(jù)的可見性
*/
public class LockDemotionExample {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private volatile boolean update = false;
private Object data;
public void processData() {
readLock.lock();
if (!update) {
// 數(shù)據(jù)需要更新,必須先釋放讀鎖
readLock.unlock();
// 獲取寫鎖
writeLock.lock();
try {
// 雙重檢查
if (!update) {
// 準(zhǔn)備數(shù)據(jù)...
data = fetchData();
update = true;
}
// 關(guān)鍵步驟:在釋放寫鎖前獲取讀鎖
readLock.lock(); // 鎖降級(jí)開始
} finally {
writeLock.unlock(); // 鎖降級(jí)完成,現(xiàn)在持有讀鎖
}
}
try {
// 使用數(shù)據(jù)(仍在讀鎖保護(hù)下)
useData(data);
} finally {
readLock.unlock();
}
}
// 不支持鎖升級(jí)!可能產(chǎn)生死鎖
public void invalidLockUpgrade() {
readLock.lock();
try {
// 危險(xiǎn)操作:嘗試在持有讀鎖時(shí)獲取寫鎖
// 如果其他線程也持有讀鎖,會(huì)產(chǎn)生死鎖
writeLock.lock(); // 可能永遠(yuǎn)阻塞!
try {
// 修改數(shù)據(jù)...
} finally {
writeLock.unlock();
}
} finally {
readLock.unlock();
}
}
private Object fetchData() { return null; }
private void useData(Object data) {}
}5. LockSupport:線程的精準(zhǔn)遙控器
5.1 LockSupport的核心能力
LockSupport提供線程阻塞和喚醒的原子操作,就像線程的遠(yuǎn)程控制器:
/**
* LockSupport基礎(chǔ)使用
*/
public class LockSupportBasic {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
System.out.println("工作線程開始執(zhí)行");
System.out.println("工作線程即將被阻塞");
// 阻塞當(dāng)前線程(停車)
LockSupport.park();
System.out.println("工作線程被喚醒,繼續(xù)執(zhí)行");
});
worker.start();
Thread.sleep(2000); // 主線程等待2秒
System.out.println("主線程準(zhǔn)備喚醒工作線程");
// 喚醒指定線程(開車)
LockSupport.unpark(worker);
System.out.println("主線程已發(fā)送喚醒信號(hào)");
}
}5.2 許可機(jī)制:先發(fā)后至的靈活性
/**
* LockSupport的許可機(jī)制演示
* 每個(gè)線程有一個(gè)許可(最多為1):
* - unpark:添加一個(gè)許可
* - park:消耗一個(gè)許可,沒有許可就阻塞
*/
public class PermitMechanism {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("子線程開始");
try {
Thread.sleep(1000); // 確保主線程先調(diào)用unpark
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("子線程調(diào)用park");
// 這里不會(huì)阻塞,因?yàn)橹骶€程已經(jīng)給了許可
LockSupport.park();
System.out.println("子線程第一次park完成");
// 這次會(huì)阻塞,因?yàn)樵S可已經(jīng)被消耗
LockSupport.park();
System.out.println("子線程第二次park完成");
});
thread.start();
// 立即給子線程許可(先發(fā))
System.out.println("主線程調(diào)用unpark");
LockSupport.unpark(thread);
// 等待后再次喚醒
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("主線程再次調(diào)用unpark");
LockSupport.unpark(thread);
}
}5.3 Blocker:線程診斷的"身份證"
/**
* Blocker的作用:在線程dump中標(biāo)識(shí)等待目標(biāo)
*/
public class BlockerDemo {
public static void main(String[] args) throws InterruptedException {
Object blocker = new Object(); // 阻塞對(duì)象
Thread withBlocker = new Thread(() -> {
System.out.println("帶Blocker的線程開始阻塞");
// 推薦:使用帶blocker的park方法
LockSupport.park(blocker);
System.out.println("帶Blocker的線程被喚醒");
}, "WithBlocker-Thread");
Thread withoutBlocker = new Thread(() -> {
System.out.println("無Blocker的線程開始阻塞");
// 不推薦:無blocker的park方法
LockSupport.park();
System.out.println("無Blocker的線程被喚醒");
}, "WithoutBlocker-Thread");
withBlocker.start();
withoutBlocker.start();
Thread.sleep(1000);
// 在實(shí)際環(huán)境中使用jstack查看線程dump,區(qū)別明顯:
// 有Blocker: "parking to wait for <0x00000000d5e8a6c0> (a java.lang.Object)"
// 無Blocker: 只顯示在LockSupport.park處等待
LockSupport.unpark(withBlocker);
LockSupport.unpark(withoutBlocker);
withBlocker.join();
withoutBlocker.join();
}
}6. Condition接口:精準(zhǔn)的線程協(xié)調(diào)器
6.1 Condition vs Object監(jiān)視器
| 特性 | Object.wait/notify | Condition.await/signal |
|---|---|---|
| 前置條件 | 必須在synchronized內(nèi) | 必須先獲取Lock |
| 等待隊(duì)列 | 一個(gè)對(duì)象一個(gè)隊(duì)列 | 一個(gè)Lock多個(gè)Condition |
| 精確通知 | 只能notifyAll或隨機(jī) | 可以精確通知特定Condition |
| 超時(shí)控制 | 有限支持 | 豐富的時(shí)間控制方法 |
6.2 Condition實(shí)戰(zhàn):有界阻塞隊(duì)列
/**
* 有界阻塞隊(duì)列 - Condition的經(jīng)典應(yīng)用
* 功能:
* - 隊(duì)列空時(shí),消費(fèi)者等待
* - 隊(duì)列滿時(shí),生產(chǎn)者等待
*/
public class BoundedBlockingQueue<T> {
private final Object[] items;
private int addIndex, removeIndex, count;
private final Lock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition(); // 非空條件
private final Condition notFull = lock.newCondition(); // 非滿條件
public BoundedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
items = new Object[capacity];
}
/**
* 生產(chǎn)方法:隊(duì)列滿時(shí)阻塞
*/
public void put(T item) throws InterruptedException {
lock.lock();
try {
// 使用while防止虛假喚醒
while (count == items.length) {
System.out.println("隊(duì)列已滿,生產(chǎn)者等待...");
notFull.await(); // 在notFull條件上等待
}
// 生產(chǎn)元素
items[addIndex] = item;
if (++addIndex == items.length) addIndex = 0;
count++;
System.out.println("生產(chǎn): " + item + ", 當(dāng)前數(shù)量: " + count);
// 通知可能等待的消費(fèi)者
notEmpty.signal();
} finally {
lock.unlock();
}
}
/**
* 消費(fèi)方法:隊(duì)列空時(shí)阻塞
*/
@SuppressWarnings("unchecked")
public T take() throws InterruptedException {
lock.lock();
try {
// 使用while防止虛假喚醒
while (count == 0) {
System.out.println("隊(duì)列為空,消費(fèi)者等待...");
notEmpty.await(); // 在notEmpty條件上等待
}
// 消費(fèi)元素
T item = (T) items[removeIndex];
if (++removeIndex == items.length) removeIndex = 0;
count--;
System.out.println("消費(fèi): " + item + ", 當(dāng)前數(shù)量: " + count);
// 通知可能等待的生產(chǎn)者
notFull.signal();
return item;
} finally {
lock.unlock();
}
}
}6.3 Condition內(nèi)部機(jī)制:等待隊(duì)列與同步隊(duì)列的協(xié)作
/**
* Condition內(nèi)部工作機(jī)制解析
*/
public class ConditionInternalMechanism {
/**
* Condition內(nèi)部有兩個(gè)重要隊(duì)列:
*
* 1. 等待隊(duì)列(Condition隊(duì)列):?jiǎn)蜗蜴湵?
* firstWaiter → [thread|nextWaiter] → [thread|nextWaiter] → lastWaiter
*
* 2. 同步隊(duì)列(AQS隊(duì)列):雙向鏈表
* head ? [prev|thread|next] ? [prev|thread|next] ? tail
*
* await過程:同步隊(duì)列 → 等待隊(duì)列
* signal過程:等待隊(duì)列 → 同步隊(duì)列
*/
/**
* await方法核心流程:
*/
public final void await() throws InterruptedException {
// 1. 創(chuàng)建節(jié)點(diǎn)加入Condition等待隊(duì)列
Node node = addConditionWaiter();
// 2. 完全釋放鎖(保存重入狀態(tài))
int savedState = fullyRelease(node);
// 3. 阻塞直到被signal或中斷
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if (checkInterruptWhileWaiting(node) != 0) break;
}
// 4. 被喚醒后重新競(jìng)爭(zhēng)鎖
if (acquireQueued(node, savedState)) {
// 處理中斷...
}
}
/**
* signal方法核心流程:
*/
public final void signal() {
// 1. 必須持有鎖才能調(diào)用
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 2. 轉(zhuǎn)移等待隊(duì)列的第一個(gè)節(jié)點(diǎn)到同步隊(duì)列
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
}7. AQS同步隊(duì)列 vs Condition等待隊(duì)列
7.1 核心區(qū)別總結(jié)
| 特性 | AQS同步隊(duì)列 | Condition等待隊(duì)列 |
|---|---|---|
| 設(shè)計(jì)目的 | 管理鎖競(jìng)爭(zhēng)的線程 | 管理?xiàng)l件等待的線程 |
| 數(shù)據(jù)結(jié)構(gòu) | 雙向鏈表 | 單向鏈表 |
| 節(jié)點(diǎn)狀態(tài) | SIGNAL, CANCELLED, 0, PROPAGATE | CONDITION(-2) |
| 隊(duì)列數(shù)量 | 每個(gè)AQS實(shí)例1個(gè) | 每個(gè)Condition1個(gè) |
| 線程行為 | 競(jìng)爭(zhēng)鎖資源 | 等待特定條件滿足 |
| 喚醒方式 | 前驅(qū)節(jié)點(diǎn)釋放鎖時(shí)喚醒 | 其他線程調(diào)用signal()喚醒 |
7.2 隊(duì)列協(xié)作的完整示例
/**
* 演示兩個(gè)隊(duì)列如何協(xié)作工作
*/
public class TwoQueuesCollaboration {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void demonstrate() throws InterruptedException {
Thread waiter = new Thread(() -> {
lock.lock();
try {
System.out.println("Waiter: 獲取鎖,準(zhǔn)備await");
// 此時(shí):
// - Waiter在同步隊(duì)列中(持有鎖)
// - 等待隊(duì)列為空
condition.await(); // 關(guān)鍵操作!
// await內(nèi)部完成:
// 1. Waiter節(jié)點(diǎn)從同步隊(duì)列移到等待隊(duì)列
// 2. 釋放鎖
// 3. 阻塞等待
System.out.println("Waiter: 被喚醒,重新獲得鎖");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
});
Thread signaler = new Thread(() -> {
lock.lock();
try {
System.out.println("Signaler: 獲取鎖,調(diào)用signal");
// 此時(shí):
// - Signaler在同步隊(duì)列中(持有鎖)
// - Waiter在等待隊(duì)列中
condition.signal(); // 關(guān)鍵操作!
// signal內(nèi)部完成:
// 1. Waiter節(jié)點(diǎn)從等待隊(duì)列移到同步隊(duì)列
// 2. 喚醒Waiter線程
System.out.println("Signaler: signal完成");
} finally {
lock.unlock(); // 釋放鎖,Waiter有機(jī)會(huì)競(jìng)爭(zhēng)鎖
}
});
waiter.start();
Thread.sleep(100); // 確保waiter先執(zhí)行
signaler.start();
waiter.join();
signaler.join();
}
}8. 實(shí)戰(zhàn)指南:如何正確使用Java并發(fā)鎖
8.1 鎖使用的核心原則
原則1:永遠(yuǎn)在finally塊中釋放鎖
/**
* 正確的鎖釋放方式
*/
public class CorrectLockUsage {
private final Lock lock = new ReentrantLock();
// ? 正確做法
public void correctMethod() {
lock.lock();
try {
// 業(yè)務(wù)邏輯
doBusiness();
} finally {
lock.unlock(); // 確保鎖被釋放
}
}
// ? 錯(cuò)誤做法
public void wrongMethod() {
lock.lock();
doBusiness();
lock.unlock(); // 如果doBusiness拋出異常,鎖不會(huì)被釋放!
}
private void doBusiness() {
// 可能拋出異常的業(yè)務(wù)邏輯
if (Math.random() > 0.5) {
throw new RuntimeException("業(yè)務(wù)異常");
}
}
}原則2:避免鎖嵌套,預(yù)防死鎖
/**
* 死鎖預(yù)防示例
*/
public class DeadlockPrevention {
private final Lock lockA = new ReentrantLock();
private final Lock lockB = new ReentrantLock();
// ? 容易導(dǎo)致死鎖的做法
public void potentialDeadlock() {
lockA.lock();
try {
// 一些操作...
lockB.lock(); // 如果另一個(gè)線程以相反順序獲取鎖,可能死鎖
try {
// 更多操作...
} finally {
lockB.unlock();
}
} finally {
lockA.unlock();
}
}
// ? 使用tryLock避免死鎖
public void safeMethod() {
while (true) {
if (lockA.tryLock()) {
try {
if (lockB.tryLock()) {
try {
// 成功獲取兩個(gè)鎖,執(zhí)行業(yè)務(wù)
doBusiness();
return; // 成功完成,退出循環(huán)
} finally {
lockB.unlock();
}
}
} finally {
lockA.unlock();
}
}
// 獲取鎖失敗,短暫休息后重試
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
// ? 使用固定的鎖獲取順序
public void fixedOrderMethod(Object resource1, Object resource2) {
// 通過hash值確定固定的獲取順序
int hash1 = System.identityHashCode(resource1);
int hash2 = System.identityHashCode(resource2);
if (hash1 < hash2) {
lockA.lock();
lockB.lock();
} else {
lockB.lock();
lockA.lock();
}
try {
doBusiness();
} finally {
lockA.unlock();
lockB.unlock();
}
}
private void doBusiness() {}
}8.2 各組件最佳實(shí)踐案例
8.2.1 ReentrantLock最佳實(shí)踐:連接池管理
/**
* 基于ReentrantLock的簡(jiǎn)單數(shù)據(jù)庫連接池
* 特性:支持超時(shí)獲取、中斷響應(yīng)、連接驗(yàn)證
*/
public class DatabaseConnectionPool {
private final LinkedList<Connection> pool = new LinkedList<>();
private final ReentrantLock lock = new ReentrantLock(true); // 公平鎖
private final Condition notEmpty = lock.newCondition();
private final int maxSize;
private final long maxWaitTime;
public DatabaseConnectionPool(int maxSize, long maxWaitMillis) {
this.maxSize = maxSize;
this.maxWaitTime = maxWaitMillis;
initializePool();
}
/**
* 獲取連接 - 支持超時(shí)和中斷
*/
public Connection getConnection() throws InterruptedException, TimeoutException {
lock.lock();
try {
long endTime = System.currentTimeMillis() + maxWaitTime;
while (pool.isEmpty()) {
long remainingTime = endTime - System.currentTimeMillis();
if (remainingTime <= 0) {
throw new TimeoutException("獲取連接超時(shí)");
}
// 等待連接可用,支持超時(shí)
if (!notEmpty.await(remainingTime, TimeUnit.MILLISECONDS)) {
throw new TimeoutException("獲取連接超時(shí)");
}
}
// 獲取并驗(yàn)證連接
Connection conn = pool.removeFirst();
if (!isConnectionValid(conn)) {
conn = createNewConnection();
}
return conn;
} finally {
lock.unlock();
}
}
/**
* 歸還連接
*/
public void returnConnection(Connection conn) {
if (conn == null) return;
lock.lock();
try {
if (pool.size() < maxSize && isConnectionValid(conn)) {
pool.addLast(conn);
notEmpty.signal(); // 通知等待的線程
} else {
// 連接池已滿或連接無效,關(guān)閉連接
closeConnection(conn);
}
} finally {
lock.unlock();
}
}
/**
* 嘗試獲取連接(非阻塞)
*/
public Connection tryGetConnection() {
if (lock.tryLock()) {
try {
if (!pool.isEmpty()) {
Connection conn = pool.removeFirst();
if (isConnectionValid(conn)) {
return conn;
}
}
} finally {
lock.unlock();
}
}
return null;
}
private boolean isConnectionValid(Connection conn) {
// 連接有效性檢查邏輯
try {
return conn != null && !conn.isClosed() && conn.isValid(2);
} catch (SQLException e) {
return false;
}
}
private Connection createNewConnection() {
// 創(chuàng)建新連接的邏輯
return null; // 簡(jiǎn)化實(shí)現(xiàn)
}
private void closeConnection(Connection conn) {
// 關(guān)閉連接的邏輯
}
private void initializePool() {
// 初始化連接池
for (int i = 0; i < maxSize / 2; i++) {
pool.add(createNewConnection());
}
}
}8.2.2 讀寫鎖最佳實(shí)踐:配置中心
/**
* 基于讀寫鎖的配置中心
* 特性:高頻讀取,低頻更新,保證數(shù)據(jù)一致性
*/
public class ConfigurationCenter {
private final Map<String, String> configs = new HashMap<>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
/**
* 獲取配置 - 高頻操作,使用讀鎖
*/
public String getConfig(String key) {
readLock.lock();
try {
String value = configs.get(key);
// 模擬配置讀取的耗時(shí)操作
simulateProcess(1);
return value;
} finally {
readLock.unlock();
}
}
/**
* 批量獲取配置 - 讀鎖支持并發(fā)
*/
public Map<String, String> getConfigs(Set<String> keys) {
readLock.lock();
try {
Map<String, String> result = new HashMap<>();
for (String key : keys) {
result.put(key, configs.get(key));
}
simulateProcess(keys.size());
return result;
} finally {
readLock.unlock();
}
}
/**
* 更新配置 - 低頻操作,使用寫鎖
*/
public void updateConfig(String key, String value) {
writeLock.lock();
try {
// 模擬配置更新的耗時(shí)操作
simulateProcess(10);
configs.put(key, value);
System.out.println("配置更新: " + key + " = " + value);
} finally {
writeLock.unlock();
}
}
/**
* 批量更新配置 - 寫鎖保證原子性
*/
public void updateConfigs(Map<String, String> newConfigs) {
writeLock.lock();
try {
simulateProcess(newConfigs.size() * 5);
configs.putAll(newConfigs);
System.out.println("批量更新配置: " + newConfigs.size() + " 項(xiàng)");
} finally {
writeLock.unlock();
}
}
/**
* 熱更新配置 - 使用鎖降級(jí)保證數(shù)據(jù)一致性
*/
public void hotUpdateConfig(String key, String value) {
// 先獲取寫鎖進(jìn)行更新
writeLock.lock();
try {
// 更新配置
configs.put(key, value);
System.out.println("熱更新配置: " + key + " = " + value);
// 鎖降級(jí):在釋放寫鎖前獲取讀鎖
readLock.lock();
} finally {
writeLock.unlock(); // 鎖降級(jí)完成
}
try {
// 在讀鎖保護(hù)下進(jìn)行后續(xù)操作(如通知觀察者、記錄日志等)
notifyConfigChange(key, value);
logConfigChange(key, value);
} finally {
readLock.unlock();
}
}
private void notifyConfigChange(String key, String value) {
// 通知配置變更觀察者
simulateProcess(2);
}
private void logConfigChange(String key, String value) {
// 記錄配置變更日志
simulateProcess(1);
}
private void simulateProcess(int milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}8.2.3 Condition最佳實(shí)踐:任務(wù)調(diào)度器
/**
* 基于Condition的任務(wù)調(diào)度器
* 特性:支持任務(wù)等待、超時(shí)控制、優(yōu)雅關(guān)閉
*/
public class TaskScheduler {
private final PriorityBlockingQueue<Task> taskQueue = new PriorityBlockingQueue<>();
private final ReentrantLock lock = new ReentrantLock();
private final Condition taskAvailable = lock.newCondition();
private final Condition schedulerStopped = lock.newCondition();
private volatile boolean running = true;
private final Thread workerThread;
public TaskScheduler() {
this.workerThread = new Thread(this::processTasks, "TaskScheduler-Worker");
this.workerThread.start();
}
/**
* 提交任務(wù) - 支持超時(shí)
*/
public boolean submitTask(Task task, long timeout, TimeUnit unit)
throws InterruptedException {
lock.lock();
try {
long deadline = System.nanoTime() + unit.toNanos(timeout);
// 等待直到有空間或超時(shí)
while (taskQueue.size() >= 1000) { // 隊(duì)列容量限制
long remaining = deadline - System.nanoTime();
if (remaining <= 0) {
return false; // 超時(shí)
}
if (!taskAvailable.await(remaining, TimeUnit.NANOSECONDS)) {
return false; // 超時(shí)
}
}
taskQueue.offer(task);
taskAvailable.signal(); // 通知工作線程
return true;
} finally {
lock.unlock();
}
}
/**
* 立即提交任務(wù)(非阻塞)
*/
public boolean submitTaskNow(Task task) {
lock.lock();
try {
if (taskQueue.size() >= 1000) {
return false; // 隊(duì)列已滿
}
taskQueue.offer(task);
taskAvailable.signal();
return true;
} finally {
lock.unlock();
}
}
/**
* 優(yōu)雅關(guān)閉 - 等待所有任務(wù)完成
*/
public void shutdown() throws InterruptedException {
lock.lock();
try {
running = false;
taskAvailable.signalAll(); // 喚醒工作線程
// 等待任務(wù)隊(duì)列清空
while (!taskQueue.isEmpty()) {
schedulerStopped.await(1, TimeUnit.SECONDS);
}
} finally {
lock.unlock();
}
workerThread.join();
}
/**
* 立即關(guān)閉
*/
public void shutdownNow() {
lock.lock();
try {
running = false;
taskQueue.clear();
taskAvailable.signalAll();
} finally {
lock.unlock();
}
}
private void processTasks() {
while (running || !taskQueue.isEmpty()) {
try {
Task task = taskQueue.poll(1, TimeUnit.SECONDS);
if (task != null) {
executeTask(task);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
// 通知關(guān)閉完成
lock.lock();
try {
schedulerStopped.signalAll();
} finally {
lock.unlock();
}
}
private void executeTask(Task task) {
try {
task.execute();
} catch (Exception e) {
System.err.println("任務(wù)執(zhí)行失敗: " + e.getMessage());
}
}
public static class Task implements Comparable<Task> {
private final Runnable runnable;
private final long scheduledTime;
private final int priority;
public Task(Runnable runnable, long delay, TimeUnit unit, int priority) {
this.runnable = runnable;
this.scheduledTime = System.nanoTime() + unit.toNanos(delay);
this.priority = priority;
}
public void execute() {
runnable.run();
}
@Override
public int compareTo(Task other) {
int timeCompare = Long.compare(this.scheduledTime, other.scheduledTime);
if (timeCompare != 0) {
return timeCompare;
}
return Integer.compare(other.priority, this.priority); // 優(yōu)先級(jí)高的在前
}
}
}8.2.4 LockSupport最佳實(shí)踐:自定義同步器
/**
* 基于LockSupport的自定義閉鎖
* 特性:一次性屏障,支持超時(shí)和中斷
*/
public class SimpleCountDownLatch {
private volatile int count;
private final Queue<Thread> waiters = new ConcurrentLinkedQueue<>();
public SimpleCountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("計(jì)數(shù)不能為負(fù)");
this.count = count;
}
/**
* 等待閉鎖打開
*/
public void await() throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
if (count <= 0) {
return; // 已經(jīng)打開
}
// 使用LockSupport阻塞
Thread current = Thread.currentThread();
waiters.offer(current);
// 雙重檢查避免錯(cuò)過信號(hào)
if (count > 0) {
LockSupport.park(this);
}
// 檢查是否被中斷喚醒
if (Thread.interrupted()) {
throw new InterruptedException();
}
}
/**
* 超時(shí)等待
*/
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
if (count <= 0) {
return true;
}
long deadline = System.nanoTime() + unit.toNanos(timeout);
Thread current = Thread.currentThread();
waiters.offer(current);
try {
while (count > 0) {
long remaining = deadline - System.nanoTime();
if (remaining <= 0) {
return false; // 超時(shí)
}
LockSupport.parkNanos(this, remaining);
if (Thread.interrupted()) {
throw new InterruptedException();
}
}
return true;
} finally {
waiters.remove(current);
}
}
/**
* 計(jì)數(shù)減1
*/
public void countDown() {
if (count <= 0) {
return; // 已經(jīng)打開
}
if (--count == 0) {
// 喚醒所有等待線程
Thread waiter;
while ((waiter = waiters.poll()) != null) {
LockSupport.unpark(waiter);
}
}
}
/**
* 獲取當(dāng)前計(jì)數(shù)
*/
public int getCount() {
return count;
}
}8.3 性能優(yōu)化和陷阱避免
8.3.1 鎖性能優(yōu)化技巧
/**
* 鎖性能優(yōu)化實(shí)踐
*/
public class LockPerformanceOptimization {
private final ReentrantLock lock = new ReentrantLock();
private int counter = 0;
private long lastLogTime = System.currentTimeMillis();
// ? 鎖粒度太粗
public void coarseGrainedLock() {
lock.lock();
try {
// 包含非臨界區(qū)操作
loadFromDatabase(); // 耗時(shí)IO操作
counter++; // 真正的臨界區(qū)
saveToDatabase(); // 耗時(shí)IO操作
} finally {
lock.unlock();
}
}
// ? 鎖粒度細(xì)化
public void fineGrainedLock() {
// 在鎖外執(zhí)行IO操作
loadFromDatabase();
lock.lock();
try {
counter++; // 只保護(hù)真正的臨界區(qū)
} finally {
lock.unlock();
}
saveToDatabase(); // 在鎖外執(zhí)行IO操作
}
// ? 使用原子變量替代鎖
private final AtomicInteger atomicCounter = new AtomicInteger(0);
public void atomicOperation() {
atomicCounter.incrementAndGet(); // 無鎖操作,性能更高
}
// ? 讀寫分離
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private volatile boolean initialized = false;
public void lazyInit() {
if (!initialized) {
writeLock.lock();
try {
// 雙重檢查
if (!initialized) {
performInitialization();
initialized = true;
}
} finally {
writeLock.unlock();
}
}
// 后續(xù)讀操作使用讀鎖
readLock.lock();
try {
useInitializedResource();
} finally {
readLock.unlock();
}
}
private void loadFromDatabase() {
// 模擬數(shù)據(jù)庫加載
}
private void saveToDatabase() {
// 模擬數(shù)據(jù)庫保存
}
private void performInitialization() {
// 初始化操作
}
private void useInitializedResource() {
// 使用初始化后的資源
}
}8.3.2 常見陷阱及避免方法
/**
* 并發(fā)編程常見陷阱及解決方案
*/
public class ConcurrentPitfalls {
// 陷阱1:在鎖內(nèi)調(diào)用外部方法(可能發(fā)生死鎖)
public void trap1() {
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
externalMethod(); // 危險(xiǎn)!可能獲取其他鎖
} finally {
lock.unlock();
}
}
// 解決方案:減少鎖內(nèi)代碼,只包含必要的臨界區(qū)操作
public void solution1() {
ReentrantLock lock = new ReentrantLock();
// 在鎖外準(zhǔn)備數(shù)據(jù)
Object data = prepareData();
lock.lock();
try {
// 只執(zhí)行必須同步的操作
updateSharedState(data);
} finally {
lock.unlock();
}
}
// 陷阱2:忘記在finally中釋放鎖
public void trap2() {
ReentrantLock lock = new ReentrantLock();
lock.lock();
if (someCondition()) {
return; // 忘記釋放鎖!
}
lock.unlock();
}
// 解決方案:使用try-finally模板
public void solution2() {
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
if (someCondition()) {
return;
}
// 其他操作
} finally {
lock.unlock(); // 確保釋放
}
}
// 陷阱3:在Condition.await()中使用if而不是while
public void trap3() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
boolean conditionMet = false;
lock.lock();
try {
if (!conditionMet) { // 錯(cuò)誤!應(yīng)該用while
condition.await();
}
// 可能被虛假喚醒,條件仍未滿足
} finally {
lock.unlock();
}
}
// 解決方案:總是使用while循環(huán)檢查條件
public void solution3() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
boolean conditionMet = false;
lock.lock();
try {
while (!conditionMet) { // 正確!防止虛假喚醒
condition.await();
}
// 條件確定滿足
} finally {
lock.unlock();
}
}
private boolean someCondition() {
return Math.random() > 0.5;
}
private Object prepareData() {
return new Object();
}
private void updateSharedState(Object data) {
// 更新共享狀態(tài)
}
private void externalMethod() {
// 可能獲取其他鎖的外部方法
}
}8.4 監(jiān)控和調(diào)試技巧
8.4.1 鎖監(jiān)控工具
/**
* 鎖監(jiān)控和診斷工具
*/
public class LockMonitor {
/**
* 監(jiān)控鎖競(jìng)爭(zhēng)情況
*/
public static void monitorLockContention(ReentrantLock lock, String lockName) {
new Thread(() -> {
while (true) {
try {
Thread.sleep(5000); // 每5秒檢查一次
System.out.printf("鎖[%s]監(jiān)控: %n", lockName);
System.out.printf(" - 等待隊(duì)列長(zhǎng)度: %d%n", lock.getQueueLength());
System.out.printf(" - 是否有等待線程: %b%n", lock.hasQueuedThreads());
System.out.printf(" - 是否被當(dāng)前線程持有: %b%n", lock.isHeldByCurrentThread());
System.out.printf(" - 重入次數(shù): %d%n", lock.getHoldCount());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "LockMonitor-" + lockName).start();
}
/**
* 死鎖檢測(cè)
*/
public static void detectDeadlock() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] threadIds = threadMXBean.findDeadlockedThreads();
if (threadIds != null) {
System.err.println("檢測(cè)到死鎖!");
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadIds);
for (ThreadInfo threadInfo : threadInfos) {
System.err.println("死鎖線程: " + threadInfo.getThreadName());
System.err.println("阻塞對(duì)象: " + threadInfo.getLockName());
System.err.println("阻塞者: " + threadInfo.getLockOwnerName());
}
}
}
/**
* 鎖性能統(tǒng)計(jì)
*/
public static class LockStats {
private final ReentrantLock lock;
private long lockAcquireCount = 0;
private long totalWaitTime = 0;
private long maxWaitTime = 0;
public LockStats(ReentrantLock lock) {
this.lock = lock;
}
public void recordLockAcquire(long waitTime) {
lockAcquireCount++;
totalWaitTime += waitTime;
maxWaitTime = Math.max(maxWaitTime, waitTime);
}
public void printStats() {
System.out.printf("鎖統(tǒng)計(jì): %n");
System.out.printf(" - 獲取次數(shù): %d%n", lockAcquireCount);
System.out.printf(" - 平均等待時(shí)間: %.2fms%n",
lockAcquireCount > 0 ? (double)totalWaitTime / lockAcquireCount : 0);
System.out.printf(" - 最大等待時(shí)間: %dms%n", maxWaitTime);
System.out.printf(" - 當(dāng)前等待線程: %d%n", lock.getQueueLength());
}
}
}總結(jié):正確使用并發(fā)鎖的黃金法則
- 明確性:清楚知道每個(gè)鎖保護(hù)的是什么數(shù)據(jù)
- 最小化:鎖粒度盡可能小,持有時(shí)間盡可能短
- 一致性:按照固定順序獲取多個(gè)鎖
- 可靠性:總是在finally塊中釋放鎖
- 可中斷性:對(duì)長(zhǎng)時(shí)間操作使用可中斷的鎖獲取方式
- 監(jiān)控性:對(duì)關(guān)鍵鎖進(jìn)行監(jiān)控和統(tǒng)計(jì)
Java并發(fā)包中的鎖機(jī)制通過精妙的設(shè)計(jì)實(shí)現(xiàn)了高效、靈活的線程同步:
- AQS是基石:提供了同步隊(duì)列管理和狀態(tài)控制的基礎(chǔ)設(shè)施
- Lock接口是門面:定義了豐富的鎖操作API
- 重入鎖解決重入問題:支持同一線程多次獲取鎖
- 讀寫鎖優(yōu)化讀多寫少場(chǎng)景:通過鎖分離提升并發(fā)性能
- LockSupport提供底層阻塞能力:線程控制的精準(zhǔn)工具
- Condition實(shí)現(xiàn)精確線程協(xié)調(diào):多條件等待隊(duì)列支持復(fù)雜同步邏輯
記住這個(gè)核心關(guān)系:Lock → AQS → Condition → LockSupport,它們共同構(gòu)成了Java并發(fā)鎖機(jī)制的完整體系。理解這些組件的內(nèi)部機(jī)制,能夠幫助我們正確選擇和使用合適的同步工具,診斷和解決復(fù)雜的并發(fā)問題,設(shè)計(jì)高性能的并發(fā)組件。
記住這些原則和最佳實(shí)踐,你就能構(gòu)建出高效、可靠的并發(fā)程序。并發(fā)編程雖然復(fù)雜,但通過正確的工具和方法,完全可以駕馭!
到此這篇關(guān)于Java并發(fā)編程之Lock鎖機(jī)制從使用到源碼實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Java Lock鎖機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring事務(wù)注解如何確保你的應(yīng)用數(shù)據(jù)的一致性
Spring事務(wù)注解用于確保電商平臺(tái)下單過程中數(shù)據(jù)一致性,通過ACID原則、傳播行為、隔離級(jí)別和回滾配置管理數(shù)據(jù)庫操作,合理設(shè)置屬性及拆分事務(wù)方法可提升系統(tǒng)穩(wěn)定性與可靠性,本文介紹Spring事務(wù)注解如何確保你的應(yīng)用數(shù)據(jù)的一致性,感興趣的朋友一起看看吧2025-07-07
基于Mybatis Plus實(shí)現(xiàn)多表分頁查詢的示例代碼
這篇文章主要介紹了基于Mybatis Plus實(shí)現(xiàn)多表分頁查詢的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Springboot+MyBatist實(shí)現(xiàn)前后臺(tái)交互登陸功能方式
這篇文章主要介紹了Springboot+MyBatist實(shí)現(xiàn)前后臺(tái)交互登陸功能方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
Spring?Boot?+?Mybatis?Plus實(shí)現(xiàn)樹狀菜單的方法
這篇文章主要介紹了Spring?Boot?+?Mybatis?Plus實(shí)現(xiàn)樹狀菜單,包括實(shí)體類中添加子菜單列表和集合及構(gòu)建菜單樹的詳細(xì)代碼,代碼簡(jiǎn)單易懂,需要的朋友可以參考下2021-12-12
Java 實(shí)現(xiàn)緩存的三種方式及問題匯總
這篇文章主要介紹了Java 實(shí)現(xiàn)緩存的三種方式及問題匯總,HashMap實(shí)現(xiàn)緩存,可以實(shí)現(xiàn)簡(jiǎn)單的本地緩存,但是實(shí)際開發(fā)中不推薦,我們可以簡(jiǎn)單模擬一下緩存的實(shí)現(xiàn),本文通過示例代碼介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-03-03
JAVA中@ApiModel和@ApiModelProperty注解實(shí)戰(zhàn)代碼
這篇文章主要給大家介紹了關(guān)于JAVA中@ApiModel和@ApiModelProperty注解的相關(guān)資料,@ApiModel注解是用在接口相關(guān)的實(shí)體類上的注解,它主要是用來對(duì)使用該注解的接口相關(guān)的實(shí)體類添加額外的描述信息,常常和@ApiModelProperty注解配合使用,需要的朋友可以參考下2024-03-03

