Java 并發鎖大起底:內置鎖、可重入鎖、讀寫鎖到底怎么選?
鎖機制不僅是技術細節,而是性能與安全的分界線
在 Java 的多線程開發中,正確選擇合適的鎖類型,不只是編程技巧,更是系統性能瓶頸的“拯救者”。錯誤的鎖策略可能導致線程阻塞、數據不一致,甚至系統宕機。本文將從 Java 對象自帶的內建鎖(Intrinsic Lock)*談起,深入探索*ReentrantLock(可重入鎖)*與*ReadWriteLock(讀寫鎖),逐層揭示它們在不同場景下的實戰意義與使用細節。
對象自帶的同步鎖:Java 的“原生防護機制”
在 Java 中,每個對象天生就攜帶一個“鎖”,這就是所謂的內建鎖或監視器鎖(Monitor Lock)。只要使用 synchronized 關鍵字,就會觸發這個鎖的加鎖/釋放機制。
實現機制解析
同步方法:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
}當多個線程調用 increment(),只有一個線程能夠執行,其它線程會被阻塞。
同步代碼塊:
public class Counter {
private int count = 0;
public void increment() {
synchronized (this) {
count++;
}
}
}相比整個方法加鎖,代碼塊方式更靈活,可控制鎖的粒度。
類比理解:廁所排隊模型
對象:廁所
線程:人
鎖:門鎖一個人用完才能輪下一個,防止多線程“同時入廁”的混亂局面。
特性小結
特性 | 說明 |
每個對象一把鎖 | 同步鎖是對象級別的,不是方法或類級別 |
可重入 | 同一線程可多次獲得鎖,不會死鎖 |
靜態同步方法 | 鎖定的是類對象 |
多線程驗證示例:
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) counter.increment();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) counter.increment();
});
t1.start(); t2.start(); t1.join(); t2.join();
System.out.println("Final count: " + counter.count);
}最終輸出應為
2000,因為同步機制阻止了競態。
ReentrantLock:功能更豐富的手動加鎖器
ReentrantLock 是 java.util.concurrent.locks 包下的高級鎖工具,它與 synchronized 相似,但提供了更多控制能力。
為什么叫“Reentrant”?
因為它允許同一個線程重復獲得同一個鎖。這在遞歸調用等場景下非常關鍵。
示例:可重入兩次不阻塞
lock.lock();
lock.lock(); // 不會死鎖,因是同一個線程
lock.unlock();
lock.unlock();核心特性
特性名稱 | 功能說明 |
公平性控制 | 是否讓最早請求鎖的線程先獲得鎖,防止“餓死” |
tryLock | 非阻塞獲取鎖,失敗立即返回 |
lockInterruptibly | 可響應中斷的加鎖方式,適用于超時/取消任務的場景 |
條件變量支持 | 可基于 Condition 精細喚醒/等待多個線程集 |
實戰代碼:
public class Counter {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}使用示例:
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) counter.increment();
};
Thread t1 = new Thread(task), t2 = new Thread(task);
t1.start(); t2.start(); t1.join(); t2.join();
System.out.println("Final count: " + counter.getCount());
}相比
synchronized,ReentrantLock支持更細的控制和靈活的用法。
ReadWriteLock:讀多寫少場景的并發利器
ReadWriteLock 提供了一種雙鎖機制:讀鎖允許多個線程并發獲取,寫鎖則是獨占的。
應用場景類比:教室黑板模型
- 學生(讀者)可以同時看黑板
- 老師(寫者)在寫字時,學生必須停下來看 這正是
ReadWriteLock的工作方式。
構造與用法
private final ReadWriteLock lock = new ReentrantReadWriteLock();讀操作:
public int readData() {
lock.readLock().lock();
try {
return data;
} finally {
lock.readLock().unlock();
}
}寫操作:
public void writeData(int newData) {
lock.writeLock().lock();
try {
data = newData;
} finally {
lock.writeLock().unlock();
}
}多線程示例:
public static void main(String[] args) {
SharedResource resource = new SharedResource();
Runnable reader = () -> resource.readData();
Runnable writer = () -> resource.writeData(42);
new Thread(reader, "Reader-1").start();
new Thread(reader, "Reader-2").start();
new Thread(writer, "Writer-1").start();
}特性總結
特性類別 | 說明 |
讀鎖 | 多線程可同時讀 |
寫鎖 | 僅一個線程寫,寫時無讀 |
適用場景 | 讀遠多于寫的場景,如緩存、配置、狀態查詢等 |
總結:鎖的選擇決定系統表現
類型 | 并發控制能力 | 使用復雜度 | 性能 | 推薦場景 |
| 簡單,JVM原生支持 | ? | 一般 | 新手入門、小型項目 |
| 可中斷、公平鎖等 | ?? | 好 | 需要更高控制靈活性 |
| 支持讀寫分離 | ??? | 高 | 讀多寫少的場景 |
在現代 Java 并發開發中,鎖的選擇不是“誰最強”決定的,而是“誰最適合你當前的使用場景”來定的。了解每種鎖的底層機制與使用邊界,是開發者構建高性能、高并發系統的必要修煉。
























