鎖相關
?1. 什么是可重入鎖?Java 中如何實現???
?答?:
可重入鎖允許一個線程多次獲取同一把鎖(即遞歸調用時無需重新競爭鎖)。
- ?關鍵點?:防止死鎖,避免線程因重復請求已持有的鎖而阻塞。
- ?Java 實現?:
- synchronized:隱式支持可重入。
public synchronized void methodA() {
methodB(); // 可重入
}
public synchronized void methodB() {} - ReentrantLock:顯式鎖,通過計數器實現可重入。
ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock();
try {
// 遞歸調用或其他同步代碼
} finally {
lock.unlock();
}
}
?2. ReentrantLock 與 synchronized 的區別???
?
?3. 什么是讀寫鎖(ReadWriteLock)???
?答?:
讀寫鎖分離讀操作(共享)和寫操作(獨占),提升并發性能。
- ?規則?:
- 讀鎖:允許多線程同時讀,但排斥寫鎖。
- 寫鎖:獨占鎖,排斥其他所有讀寫鎖。
- ?適用場景?:讀多寫少(如緩存)。
- ?Java 實現?:ReentrantReadWriteLock
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public void readData() {
rwLock.readLock().lock();
try {
// 讀操作
} finally {
rwLock.readLock().unlock();
}
}
public void writeData() {
rwLock.writeLock().lock();
try {
// 寫操作
} finally {
rwLock.writeLock().unlock();
}
}
?4. 死鎖產生的條件?如何避免???
?死鎖條件?:
1.?互斥?:資源獨占。
2.?持有且等待?:線程持有資源并等待其他資源。
3.?不可搶占?:資源只能由持有線程釋放。
4.?循環等待?:多個線程形成資源請求閉環。
?避免方法?: - ?破壞循環等待?:按固定順序獲取鎖(如按鎖對象的哈希值排序)。
public void transfer(Account a, Account b, int amount) {
Object firstLock = a.hashCode() < b.hashCode() ? a : b;
Object secondLock = firstLock == a ? b : a;
synchronized (firstLock) {
synchronized (secondLock) {
// 轉賬操作
}
}
} - ?超時機制?:使用 tryLock() 設置超時時間。
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try { /* … */ } finally { lock2.unlock(); }
}
} finally { lock1.unlock(); }
} - ?銀行家算法?:動態檢查資源分配是否安全(實際開發較少用)。
?5. 樂觀鎖與悲觀鎖的區別???
?
?CAS 示例?:
AtomicInteger count = new AtomicInteger(0);
public void increment() {
int oldVal, newVal;
do {
oldVal = count.get();
newVal = oldVal + 1;
} while (!count.compareAndSet(oldVal, newVal)); // CAS 更新
}
?6. synchronized 鎖升級過程???
1.?無鎖?:初始狀態。
2.?偏向鎖?:鎖被同一線程多次訪問時,記錄線程 ID(避免 CAS 操作)。
3.?輕量級鎖?:當多線程競爭時,通過 CAS 自旋嘗試獲取鎖(減少阻塞)。
4.?重量級鎖?:自旋超過閾值后,轉為操作系統級互斥鎖(線程阻塞)。
-
?目的?:平衡性能與開銷,減少直接使用重量級鎖的成本。
?7. volatile 能否替代鎖???
?答?:?不能完全替代。 -
?volatile 特性?:
-
保證可見性:修改后立即刷新到主內存。
-
禁止指令重排序(內存屏障)。
-
?不足?:
-
不保證原子性(如 i++ 需配合鎖或原子類)。
-
無法實現互斥訪問(如臨界區保護)。
?適用場景?: -
狀態標記(如 boolean flag = true)。
-
單次讀寫操作(如 long、double 的 64 位寫入)。
?8. 什么是自旋鎖???
?答?:線程在獲取鎖失敗時,不立即阻塞而是循環(自旋)重試,避免上下文切換開銷。 -
?實現?:
public class SpinLock {
private AtomicReference owner = new AtomicReference<>();public void lock() {
Thread t = Thread.currentThread();
while (!owner.compareAndSet(null, t)) {
// 自旋等待
}
}
public void unlock() {
owner.set(null);
}
} -
?適用場景?:鎖持有時間短、線程數少。
-
?缺點?:長時間自旋浪費 CPU(JDK 的自旋鎖有自適應策略)。
?9. StampedLock 是什么???
?答?:Java 8 引入的高性能讀寫鎖,支持三種模式:
1.?寫鎖?:獨占鎖(類似 ReentrantReadWriteLock 的寫鎖)。
2.?悲觀讀鎖?:共享鎖(類似讀鎖)。
3.?樂觀讀?:無鎖操作,需驗證是否有寫發生。
StampedLock sl = new StampedLock();
// 樂觀讀
long stamp = sl.tryOptimisticRead();
if (!sl.validate(stamp)) { // 檢查期間是否有寫操作
stamp = sl.readLock(); // 轉為悲觀讀
try { /* … */ } finally { sl.unlockRead(stamp); }
}
- ?優勢?:樂觀讀避免鎖開銷,提升讀性能。
?10. Condition 接口的作用???
?答?:配合 ReentrantLock 實現線程等待/喚醒機制,可替代 Object.wait()/notify()。 - ?優勢?:支持多個等待隊列(如生產者-消費者模型)。
java運行復制ReentrantLock lock = new ReentrantLock();
Condition notFull = lock.newCondition(); // 隊列未滿條件
Condition notEmpty = lock.newCondition(); // 隊列非空條件
// 生產者
public void put(Object item) {
lock.lock();
try {
while (queue.isFull()) notFull.await(); // 等待未滿
queue.add(item);
notEmpty.signal(); // 喚醒消費者
} finally { lock.unlock(); }
}
// 消費者
public Object take() {
lock.lock();
try {
while (queue.isEmpty()) notEmpty.await(); // 等待非空
notFull.signal(); // 喚醒生產者
return queue.remove();
} finally { lock.unlock(); }
}
?11. 鎖消除(Lock Elision)和鎖粗化(Lock Coarsening)是什么???
- ?鎖消除?:JIT 編譯器移除不必要的鎖(如局部變量鎖)。
public String concat(String s1, String s2) {
StringBuffer sb = new StringBuffer(); // 局部變量,線程安全
sb.append(s1).append(s2);
return sb.toString();
}
// JIT 可能移除 StringBuffer 的同步操作 - ?鎖粗化?:合并多次加鎖/解鎖操作為一次(減少開銷)。
synchronized (obj) { /* 操作1 / }
synchronized (obj) { / 操作2 / }
// 優化后 =>
synchronized (obj) { / 操作1+操作2 */ }
?12. 如何檢測死鎖???
1.?命令行工具?: - jstack :查看線程堆棧,標記死鎖信息。
- jconsole:圖形化檢測死鎖。
2.?代碼檢測?:
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] threadIds = bean.findDeadlockedThreads();
if (threadIds != null) {
// 處理死鎖
}
總結 - ?基礎鎖?:synchronized(隱式)、ReentrantLock(顯式)。
- ?高級鎖?:ReadWriteLock、StampedLock、Condition。
- ?鎖優化?:鎖升級、消除、粗化。
- ?避免死鎖?:順序加鎖、超時機制。
- ?工具?:樂觀鎖(CAS)、原子類、并發工具包(java.util.concurrent)。
synchronized和lock
synchronized vs Lock(以ReentrantLock為例)詳解
?1. 實現機制?
?
?2. 使用方式?
// ========== synchronized ==========
// 1. 方法級鎖
public synchronized void syncMethod() { /…/ }
// 2. 代碼塊鎖
public void method() {
synchronized(this) { // 鎖對象可以是任意對象
// 臨界區代碼
}
}
// ========== Lock ==========
private final Lock lock = new ReentrantLock();
public void lockMethod() {
lock.lock(); // 必須手動加鎖
try {
// 臨界區代碼
} finally {
lock.unlock(); // 必須放在finally中保證釋放
}
}
?3. 核心功能對比?
?
?4. 性能差異?
- ?Java 5 之前?:synchronized 性能遠低于 Lock(線程阻塞涉及內核態切換)
- ?Java 6 及以后?:
- synchronized 引入鎖升級機制?(偏向鎖→輕量級鎖→重量級鎖)
- 兩者性能差距大幅縮小
- 建議:
- ?簡單場景? → 優先用 synchronized(簡潔安全)
- ?復雜場景? → 用 Lock(利用高級功能)
?5. 鎖升級過程(synchronized優化)??
圖片代碼首次獲取鎖其他線程競爭自旋超過閾值無鎖狀態偏向鎖輕量級鎖:自旋適應重量級鎖:線程阻塞
?6. 條件變量對比?
// ===== synchronized 單條件 =====
synchronized(lockObj) {
while(!condition) lockObj.wait(); // 只能使用一個條件
// 業務代碼
lockObj.notifyAll();
}
// ===== Lock 多條件 =====
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
// 生產者
lock.lock();
try {
while(queue.isFull()) notFull.await(); // 特定條件等待
// 生產操作
notEmpty.signal(); // 喚醒特定條件
} finally { lock.unlock(); }
// 消費者
lock.lock();
try {
while(queue.isEmpty()) notEmpty.await();
// 消費操作
notFull.signal();
} finally { lock.unlock(); }
?7. 適用場景總結?
?
?8. 關鍵差異總結?
?
💡 ?最終建議?:
- 優先用 ?synchronized?(簡潔安全,JVM持續優化)
- 遇到以下需求時改用 ?Lock?:
? 需要等待超時/中斷響應
? 分組喚醒線程
? 公平鎖需求
? 需要獲取鎖的詳細信息
鎖升級過程中什么是偏向鎖→輕量級鎖→重量級鎖?什么是自旋?
鎖升級過程詳解(從偏向鎖→輕量級鎖→重量級鎖)
?1. 偏向鎖(Biased Locking)??
- ?目的?:消除無競爭情況下的同步開銷
- ?適用場景?:單線程多次訪問同步塊
- ?實現原理?:
// 對象頭存儲結構
|-------------------------------------------------------|
| Mark Word (64 bits) |
|-------------------------------------------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |
|-------------------------------------------------------| - 首次獲取鎖時,JVM將線程ID寫入對象頭(biased_lock=1, lock=01)
- 后續進入同步塊時直接比對線程ID,無需CAS操作
- ?特點?:
- 只需一次CAS開銷(首次)
- 單線程重復訪問無同步成本
- ?示例?:
synchronized(lock) { /* 第1次進入:CAS設置偏向鎖 / }
synchronized(lock) { / 第2次進入:直接檢查線程ID */ }
?2. 輕量級鎖(Lightweight Locking)?? - ?觸發條件?:第二個線程嘗試獲取鎖(偏向鎖撤銷)
- ?實現原理?:
a.JVM在線程棧幀中創建Lock Record
b.復制對象頭到Lock Record(Displaced Mark Word)
c.CAS嘗試將對象頭替換為指向Lock Record的指針
d.成功獲取鎖(lock狀態置為00) - ?鎖競爭處理?:
while (true) {
if (嘗試CAS成功) { break; }
else if (自旋超過閾值) { 升級為重量級鎖 }
else { continue; } // 繼續自旋
} - ?特點?:
- 通過自旋避免線程阻塞
- 適合短時間持有鎖的場景
- 自旋消耗CPU但減少線程切換
?3. 重量級鎖(Heavyweight Locking)?? - ?觸發條件?:自旋失敗或等待線程數過多
- ?實現原理?:
圖片代碼Object Header指向Monitor對象Owner:持有鎖的線程EntryList:競爭隊列WaitSet:等待隊列 - 依賴操作系統的互斥量(mutex)
- 未獲鎖線程直接阻塞(用戶態→內核態切換)
- ?特點?:
- 開銷大(涉及上下文切換)
- 防止CPU空轉
- 支持更復雜的同步機制
?自旋(Spinning)詳解?
?1. 核心思想?
-
?不阻塞線程,而是在循環中重試獲取鎖
-
示例自旋鎖實現:
public class SpinLock {
private AtomicReference owner = new AtomicReference<>();public void lock() {
Thread t = Thread.currentThread();
while (!owner.compareAndSet(null, t)) {
// 空循環重試(自旋)
}
}public void unlock() {
owner.set(null);
}
}
?2. 自旋策略?
?
?3. 開銷分析?
?
?4. 最佳實踐?
1.?短任務優先自旋?
if (!lock.tryLock()) {
for (int i=0; i<MAX_SPIN; i++) { // 限制自旋次數
if (lock.tryLock()) return;
}
lock.lock(); // 自旋失敗后阻塞
}
2.?結合條件檢查?
while (true) {
if (tryAcquireLock()) break;
if (needToBlock()) {
parkThread(); // 放棄自旋
break;
}
}
3.?JVM自適應優化?
- 歷史數據:統計鎖持有時間
- 動態調整:自旋時間 ≈ 上次持有時間 * 系數
- 平臺相關:單核CPU直接禁用自旋
?
?關鍵點總結?
1.?鎖升級?
- 目標:最小化同步開銷
- 路徑:無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖
- 不可逆:重量級鎖無法降級(部分JVM支持降級但成本高)
2.?自旋優化? - 目的:避免用戶態→內核態切換
- 權衡:CPU時間 vs 上下文切換開銷
- 臨界點:鎖持有時間 ≈ 線程切換時間 * 2
💡 ?實戰建議?:
undefined.減少競爭:縮小同步塊范圍
undefined.分離熱點:讀寫鎖分離(如ConcurrentHashMap)
undefined.避免升級:控制鎖持有時間短于自旋閾值
undefined.監控工具:jstack -l 檢查鎖狀態