在 Java 并發編程中,鎖(Lock)是保證線程安全的關鍵工具。本文將全面介紹 Java 的鎖機制,包括 synchronized
關鍵字、Lock
接口及其實現、讀寫鎖、樂觀鎖與悲觀鎖等,幫助新手理解 Java 并發控制。
1. Java 中的鎖概述
鎖(Lock)用于控制多個線程對共享資源的訪問。不同的鎖機制可以提供不同的性能、可重入性、公平性和可中斷性等特性。
2. synchronized
關鍵字
synchronized
是 Java 內置的同步機制,依賴于 Java 虛擬機(JVM)實現。
2.1 用法示例
public class SynchronizedExample {private int count = 0;public synchronized void increment() {count++;}
}
在上述示例中,increment
方法是同步方法,多個線程調用時會自動加鎖,保證 count
變量的線程安全。
2.2 synchronized
作用范圍
- 同步實例方法:鎖住當前實例(
this
)。 - 同步靜態方法:鎖住類對象(
Class
)。 - 同步代碼塊:可以鎖定特定對象,提高并發性。
public void method() {synchronized (this) {// 代碼塊}
}
2.3 synchronized
的特性
- 可重入性:一個線程獲取鎖后可以多次進入同步代碼。
- 不可中斷:線程獲取鎖后,其他線程只能等待。
- JVM 層面實現:使用
monitorenter
和monitorexit
指令。
3. Lock
接口(顯式鎖)
Lock
接口提供比 synchronized
更靈活的鎖控制,主要實現類是 ReentrantLock
。
3.1 ReentrantLock
用法
import java.util.concurrent.locks.ReentrantLock;public class LockExample {private final ReentrantLock lock = new ReentrantLock();private int count = 0;public void increment() {lock.lock();try {count++;} finally {lock.unlock();}}
}
3.2 ReentrantLock
特性
- 可重入性:和
synchronized
類似,一個線程可以多次獲得相同的鎖。 - 可中斷:支持
lockInterruptibly()
方法,中斷等待鎖的線程。 - 公平鎖和非公平鎖:默認非公平鎖,可選擇公平鎖保證線程按請求順序獲取鎖。
ReentrantLock fairLock = new ReentrantLock(true); // 公平鎖
4. ReadWriteLock
(讀寫鎖)
ReadWriteLock
提供讀鎖(多個線程可讀)和寫鎖(獨占)。
import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLockExample {private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();private int data = 0;public int read() {lock.readLock().lock();try {return data;} finally {lock.readLock().unlock();}}public void write(int value) {lock.writeLock().lock();try {data = value;} finally {lock.writeLock().unlock();}}
}
5. 樂觀鎖與悲觀鎖
5.1 悲觀鎖
認為競爭嚴重,每次訪問資源都加鎖(synchronized
、Lock
)。
5.2 樂觀鎖
認為競爭較少,使用 CAS(Compare And Swap) 機制,比如 AtomicInteger
。
import java.util.concurrent.atomic.AtomicInteger;public class AtomicExample {private final AtomicInteger count = new AtomicInteger(0);public void increment() {count.incrementAndGet();}
}
6. StampedLock
(改進的讀寫鎖)
StampedLock
提供樂觀讀鎖,提高并發性能。
import java.util.concurrent.locks.StampedLock;public class StampedLockExample {private final StampedLock lock = new StampedLock();private int data = 0;public int read() {long stamp = lock.tryOptimisticRead();int currentData = data;if (!lock.validate(stamp)) { // 檢測數據是否被修改lock.readLock();try {currentData = data;} finally {lock.unlockRead(stamp);}}return currentData;}
}
7. ThreadLocal
變量
ThreadLocal
不是鎖,而是讓每個線程擁有自己的變量副本,避免鎖競爭。
public class ThreadLocalExample {private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);public void increment() {threadLocal.set(threadLocal.get() + 1);}
}
8. 選擇合適的鎖
鎖類型 | 特性 | 適用場景 |
---|---|---|
synchronized | JVM 層面,簡單易用 | 適合簡單同步需求 |
ReentrantLock | 可中斷、支持公平鎖 | 適合需要高級控制的場景 |
ReadWriteLock | 讀寫分離,提高并發 | 讀多寫少的情況 |
StampedLock | 樂觀讀,提高性能 | 適合高并發讀的場景 |
ThreadLocal | 線程私有,無鎖 | 線程隔離數據 |
9. 總結
Java 提供了多種鎖機制,每種鎖都有其適用場景。synchronized
適用于簡單同步,Lock
提供更多控制,ReadWriteLock
適用于讀多寫少的情況,StampedLock
提供樂觀讀鎖以提高并發性能。此外,ThreadLocal
可用于無鎖并發,避免數據競爭。