在 Java 中,Lock 是一個接口,它提供了比 synchronized 關鍵字更靈活、更強大的線程同步機制。以下將詳細介紹 Lock 接口及其實現類,以及它與 synchronized 相比的優點。
Lock 接口及其實現類介紹
Lock 接口
Lock 接口定義了一系列用于獲取和釋放鎖的方法,主要方法如下:
- void lock():獲取鎖,如果鎖不可用,則當前線程將被阻塞,直到鎖被釋放。
- void lockInterruptibly():可中斷地獲取鎖,在獲取鎖的過程中,如果當前線程被中斷,則會拋出 InterruptedException 異常。
- boolean tryLock():嘗試非阻塞地獲取鎖,如果鎖可用,則獲取鎖并返回 true;否則返回 false。
- boolean tryLock(long time, TimeUnit unit):在指定的時間內嘗試獲取鎖,如果在該時間內鎖可用,則獲取鎖并返回 true;否則返回 false。如果在等待過程中線程被中斷,則會拋出 InterruptedException 異常。
- void unlock():釋放鎖。
- Condition newCondition():返回一個與該鎖關聯的 Condition 對象,用于實現線程間的等待 - 通知機制。
常見實現類
- ReentrantLock:可重入鎖,是 Lock 接口最常用的實現類。它支持與 synchronized 相同的可重入特性,即同一個線程可以多次獲取同一把鎖而不會被阻塞。
- ReentrantReadWriteLock:讀寫鎖,它維護了一對鎖,一個讀鎖和一個寫鎖。多個線程可以同時獲取讀鎖,但寫鎖是排他的,即同一時間只能有一個線程獲取寫鎖,并且在寫鎖被持有時,其他線程不能獲取讀鎖或寫鎖。
Lock 與 synchronized 對比的優點
1. 靈活性更高
- 鎖的獲取和釋放可分離:synchronized 是基于代碼塊或方法的,鎖的獲取和釋放是隱式的,由 JVM 自動完成。而 Lock 接口的 lock() 和 unlock() 方法可以在不同的代碼塊中調用,這使得鎖的獲取和釋放更加靈活。
例如,在某些復雜的業務邏輯中,可能需要在不同的條件下釋放鎖,使用 Lock 可以很方便地實現這一點。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockFlexibilityExample {private final Lock lock = new ReentrantLock();public void complexOperation() {lock.lock();try {// 執行一些操作if (someCondition()) {return;}// 繼續執行其他操作} finally {lock.unlock();}}private boolean someCondition() {// 模擬條件判斷return Math.random() > 0.5;}
}
在上述代碼中,如果 someCondition() 方法返回 true,則會提前退出方法,但在 finally 塊中仍然可以確保鎖被釋放。
- 可中斷的鎖獲取:Lock 接口提供了 lockInterruptibly() 方法,允許線程在獲取鎖的過程中被中斷。而 synchronized 關鍵字在獲取鎖時是不可中斷的,一旦線程進入阻塞狀態,就只能等待鎖被釋放。這在某些場景下非常有用,例如當線程需要響應中斷信號來執行其他操作時。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class InterruptibleLockExample {private final Lock lock = new ReentrantLock();public void interruptibleTask() throws InterruptedException {lock.lockInterruptibly();try {// 執行任務System.out.println("獲取到鎖,執行任務");} finally {lock.unlock();}}public static void main(String[] args) {InterruptibleLockExample example = new InterruptibleLockExample();Thread thread = new Thread(() -> {try {example.interruptibleTask();} catch (InterruptedException e) {System.out.println("線程被中斷");}});thread.start();// 中斷線程thread.interrupt();}
}
在上述代碼中,線程在獲取鎖的過程中被中斷,會拋出 InterruptedException 異常,從而可以進行相應的處理。
2. 可實現公平鎖
ReentrantLock 可以通過構造函數指定是否為公平鎖。公平鎖是指線程按照請求鎖的順序依次獲取鎖,避免了某些線程長時間得不到鎖的情況。而 synchronized 關鍵字是非公平鎖,線程獲取鎖的順序是不確定的,可能會導致某些線程饑餓。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class FairLockExample {private final Lock fairLock = new ReentrantLock(true);public void fairTask() {fairLock.lock();try {// 執行任務System.out.println(Thread.currentThread().getName() + " 獲取到公平鎖");} finally {fairLock.unlock();}}public static void main(String[] args) {FairLockExample example = new FairLockExample();for (int i = 0; i < 5; i++) {new Thread(example::fairTask, "Thread-" + i).start();}}
}
在上述代碼中,ReentrantLock 被初始化為公平鎖,線程會按照請求鎖的順序依次獲取鎖。
3. 支持多個條件變量
Lock 接口的 newCondition() 方法可以返回一個與該鎖關聯的 Condition 對象,一個 Lock 可以關聯多個 Condition 對象,從而實現更精細的線程間通信。而 synchronized 關鍵字只能使用 wait()、notify() 和 notifyAll() 方法,這些方法是基于對象的監視器,只能實現簡單的線程間等待 - 通知機制。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class ConditionExample {private final Lock lock = new ReentrantLock();private final Condition notFull = lock.newCondition();private final Condition notEmpty = lock.newCondition();private final int[] buffer = new int[10];private int count = 0;private int in = 0;private int out = 0;public void put(int value) throws InterruptedException {lock.lock();try {while (count == buffer.length) {notFull.await();}buffer[in] = value;in = (in + 1) % buffer.length;count++;notEmpty.signal();} finally {lock.unlock();}}public int take() throws InterruptedException {lock.lock();try {while (count == 0) {notEmpty.await();}int value = buffer[out];out = (out + 1) % buffer.length;count--;notFull.signal();return value;} finally {lock.unlock();}}
}
在上述代碼中,notFull 和 notEmpty 是兩個不同的 Condition 對象,分別用于控制緩沖區滿和緩沖區空的情況,實現了更精細的線程間通信。
總結
Lock 接口及其實現類提供了比 synchronized 關鍵字更靈活、更強大的線程同步機制,在需要更精細的鎖控制、可中斷的鎖獲取、公平鎖和多個條件變量等場景下,Lock 是更好的選擇。但 synchronized 關鍵字使用起來更加簡單,在一些簡單的同步場景下仍然是首選。