Java的樂觀鎖,悲觀鎖,讀寫鎖,遞歸鎖
我們都知道在 Java 中為了保證一些操作的安全性,就會涉及到使用鎖,但是你對 Java 的鎖了解的有多少呢?Java 都有哪些鎖?以及他們是怎么實現的,今天了不起就來說說關于 Java 的鎖。
樂觀鎖
樂觀鎖(Optimistic Locking)是一種在數據讀取時不會阻塞其他讀取或寫入操作的鎖策略,但在更新時會檢查在此期間是否有其他操作修改了數據。如果數據已被修改,則更新操作會失敗,通常是通過重試或拋出異常來處理。
在 Java 中,樂觀鎖通常是通過版本號、時間戳或其他狀態信息來實現的。以下是樂觀鎖在 Java 中的一些常見實現方式:
版本號機制:
- 數據表中增加一個“版本號”字段。
- 讀取數據時,同時讀取版本號。
- 更新數據時,將版本號加1,并帶上WHERE子句,確保版本號與讀取時的一致。
- 如果更新影響的行數為0,則表示在此期間數據已被其他事務修改。
時間戳機制:
- 類似于版本號,但使用時間戳字段代替。
- 更新時檢查時間戳字段,確保它與讀取時的時間戳匹配。
CAS (Compare-and-Swap) 操作:
- 是一種原子操作,用于在多線程環境中安全地更新共享變量。
- CAS操作包括三個參數:內存位置(V)、預期原值(A)和新值(B)。
- 如果內存位置V的值與預期原值A匹配,則將V的值更新為新值B。否則,不執行任何操作。
- Java 的 AtomicInteger、AtomicLong 等原子類就使用了CAS操作。
JPA 和 Hibernate 的樂觀鎖:
- JPA 和 Hibernate 提供了內置的樂觀鎖支持。
- 在實體類中添加一個版本號或時間戳字段,并使用 @Version 注解標記。
- 當 Hibernate 或 JPA 嘗試更新一個實體時,它會自動檢查版本號或時間戳字段,以確保數據在此期間沒有被其他事務修改。
悲觀鎖
悲觀鎖(Pessimistic Locking)是一種在數據處理過程中,總是假設最壞的情況來避免數據并發問題的鎖策略。在Java中,悲觀鎖通常在數據被訪問時就立即加鎖,以保證在此期間其他任何事務都不能修改這個數據,直到該事務完成為止。
Java中實現悲觀鎖的常見方式有以下幾種:
數據庫行級鎖和表級鎖:
- 行級鎖:對正在訪問的數據行加鎖,防止其他事務修改該行。這是數據庫管理系統(DBMS)提供的一種鎖機制,可以通過SQL語句來實現。
- 表級鎖:對整個表加鎖,限制其他事務對該表的并發訪問。這種鎖的開銷較小,但并發性能較低。
Java中的synchronized關鍵字:
- synchronized是Java語言內建的線程同步機制,它可以用來修飾方法或者以代碼塊的形式出現。當一個線程進入一個synchronized修飾的方法或代碼塊時,它會獲取一個鎖,其他嘗試進入該區域的線程將會被阻塞,直到第一個線程釋放鎖。
ReentrantLock類:
- Java的java.util.concurrent.locks.ReentrantLock類提供了重入鎖的實現,這是一種悲觀鎖。與synchronized相比,ReentrantLock提供了更高的靈活性,比如可以嘗試獲取鎖、定時獲取鎖以及中斷等待鎖的線程等。
讀寫鎖(ReadWriteLock):
- java.util.concurrent.locks.ReadWriteLock接口定義了讀取和寫入鎖的規則。雖然它本身不是悲觀鎖,但其中的寫鎖部分是一種悲觀鎖策略。寫鎖會阻止其他線程進行讀和寫操作,直到持有鎖的線程釋放它。
分布式鎖:
- 在分布式系統中,悲觀鎖的概念可以擴展到跨多個進程或機器。常見的實現方式包括使用Redis、Zookeeper等分布式協調服務來實現分布式鎖。
在使用悲觀鎖時,需要注意死鎖和性能問題。死鎖是指兩個或多個線程無限期地等待對方釋放資源的情況。性能問題則可能由于鎖的粒度過大(如表級鎖)導致并發性能下降。
樂觀鎖與悲觀鎖的比較:
悲觀鎖:假設最壞的情況,每次訪問數據時都會鎖定數據,防止其他事務修改。
樂觀鎖:假設最好的情況,允許其他事務并發訪問數據,但在更新時會檢查數據是否被修改。
選擇哪種鎖策略取決于應用的具體需求和并發場景。使用樂觀鎖時,需要注意處理更新失敗的情況,通常是通過重試、拋出異常或給用戶反饋來實現的。
遞歸鎖
Java中的遞歸鎖(ReentrantLock)是java.util.concurrent.locks包下提供的一種可重入的互斥鎖,它是悲觀鎖的一種實現。遞歸鎖允許一個線程多次獲取同一個鎖,而不會造成死鎖,這對于某些需要遞歸調用或者在一個線程中多次需要獲取同一個鎖的場景非常有用。
遞歸鎖的幾個特性:
可重入性:如果一個線程已經擁有了一個遞歸鎖,那么它可以再次獲取該鎖而不會阻塞。每次獲取鎖,都會增加鎖的持有計數;每次釋放鎖,都會減少持有計數。只有當持有計數減少到0時,其他線程才能獲取該鎖。
公平性:遞歸鎖可以是公平的也可以是非公平的。公平性意味著鎖的獲取是按照線程請求鎖的順序來的,而非公平性則不保證順序。公平的遞歸鎖可以減少“線程饑餓”的問題,但可能會降低性能。
既然我們說她是一個悲觀鎖的實現,那么是不是可以和 synchronized 比較一下,有什么不同呢?
與Java內置的synchronized關鍵字相比,遞歸鎖提供了更高的靈活性和更好的性能控制。例如,遞歸鎖支持嘗試獲取鎖(tryLock()方法)、定時獲取鎖(tryLock(long timeout, TimeUnit unit)方法)以及中斷等待鎖的線程(lockInterruptibly()方法)。
我們看一下遞歸鎖的示例代碼:
import java.util.concurrent.locks.ReentrantLock;
public class RecursiveLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void someMethod() {
lock.lock();
try {
// 臨界區代碼
// ...
someNestedMethod();
// ...
} finally {
lock.unlock();
}
}
private void someNestedMethod() {
lock.lock();
try {
// 嵌套調用中需要同步的代碼
// ...
} finally {
lock.unlock();
}
}
}
在上面的示例中,someMethod方法調用了someNestedMethod方法,并且兩者都需要獲取同一個遞歸鎖。由于ReentrantLock是可重入的,所以這種調用不會造成死鎖。
讀寫鎖
Java中的讀寫鎖(ReadWriteLock)是一種允許多個讀線程和單個寫線程訪問共享資源的同步機制。ReadWriteLock接口在java.util.concurrent.locks包中定義,它包含兩個鎖:一個讀鎖和一個寫鎖。
讀寫鎖的特性:
讀共享:在沒有線程持有寫鎖時,多個線程可以同時持有讀鎖來讀取共享資源。這可以提高并發性能,因為讀操作通常不會修改數據,所以允許多個讀線程并發訪問是安全的。
寫獨占:當一個線程持有寫鎖時,其他線程既不能獲取讀鎖也不能獲取寫鎖。這是為了確保寫操作對共享資源的獨占訪問,從而防止數據不一致。
Java中ReadWriteLock接口的主要實現類是ReentrantReadWriteLock,它提供了可重入的讀寫鎖實現。ReentrantReadWriteLock有兩個重要的方法:readLock()和writeLock(),分別用于獲取讀鎖和寫鎖。
我們看看示例代碼:
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private int data;
public void readData() {
lock.readLock().lock(); // 獲取讀鎖
try {
// 讀取共享資源
System.out.println("Reading data: " + data);
} finally {
lock.readLock().unlock(); // 釋放讀鎖
}
}
public void writeData(int newData) {
lock.writeLock().lock(); // 獲取寫鎖
try {
// 修改共享資源
this.data = newData;
System.out.println("Writing data: " + data);
} finally {
lock.writeLock().unlock(); // 釋放寫鎖
}
}
}
在這個例子中,readData方法使用讀鎖來讀取data字段,而writeData方法使用寫鎖來修改data字段。當多個線程調用readData時,它們可以同時讀取數據而不會相互阻塞,除非有一個線程正在調用writeData并持有寫鎖。
需要注意的是,ReentrantReadWriteLock還有一個構造方法,它接受一個布爾值參數fair,用于指定鎖是否應該是公平的。如果設置為true,則等待時間最長的線程將優先獲得鎖。但是,公平鎖可能會降低性能,因為需要維護一個有序的等待隊列。