龍目島@Locked 指南
1. 概述
Project Lombok 是一個 Java 函式庫,它提供了各種註釋,我們可以使用它們來產生標準方法和功能,從而減少樣板code 。例如,我們可以使用 Lombok 產生 getter 和 setter、建構函數,甚至可以在程式碼中引入設計模式,例如 Builder 模式。
在本教程中,我們將學習如何使用 Lombok 版本 1.18.32 中引入的@Locked註解。
2. 為什麼要使用@Locked註解?
首先,讓我們了解@Locked註解的必要性。
Java 21 引入了虛擬執行緒來簡化並發應用程式的實作、維護和調試。它們與標準執行緒的區別在於它們由 JVM 而不是作業系統管理。因此,它們的分配不需要係統調用,也不依賴作業系統的上下文切換。
然而,我們應該意識到虛擬線程可能產生的潛在效能問題。例如,當阻塞操作在synchronized區塊或方法內執行時,它仍然會阻塞作業系統的執行緒。這種情況稱為固定。另一方面,如果阻塞操作位於synchronized區塊或方法之外,則不會導致任何問題。
此外,固定會對應用程式的效能產生負面影響,特別是當阻塞操作被頻繁地呼叫且長期存在時。值得注意的是,不頻繁且短暫的阻塞操作不會導致此類問題。
解決固定問題的一種方法是將synchronized替換為ReentrantLock 。這就是新的 Lombok 註釋發揮作用的地方。
3. 理解@Locked註解
簡而言之, @Locked註釋是作為ReentrantLock的變體創建的。它的主要目的是為虛擬線程提供更好的支援。
另外,我們只能在靜態方法或實例方法上使用該註解。它將方法中提供的整個程式碼包裝到獲取鎖的區塊中。此外,我們可以指定要用於鎖定的ReentrantLock類型的欄位。結果,Lombok 對該特定欄位執行鎖定。該方法執行完成後,就會解鎖。
現在,讓我們看看@Locked註解的實際效果。
4. 依賴設定
讓我們在pom.xml中加入lombok依賴項:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
需要注意的是,我們需要 1.18.32 或更高版本才能使用此註解。
5. 使用方法
讓我們使用increment()和get()方法來建立Counter類別:
public class Counter {
private int counter = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
counter++;
} finally {
lock.unlock();
}
}
public int get() {
lock.lock();
try {
return counter;
} finally {
lock.unlock();
}
}
}
由於counter++不是原子操作,因此我們需要包含鎖定以確保共享物件的原子更新對多執行緒環境中的其他執行緒可見。否則,我們會得到不正確的結果。
這裡,我們使用ReentrantLock來鎖定increment()和get()方法,確保一次只有一個執行緒可以呼叫該方法。
讓我們替換increment()和get()方法中的鎖並使用@Locked註解:
@Locked
public void increment() {
counter++;
}
@Locked
public int get() {
return counter;
}
我們將行數減少到每個方法僅一行。現在,讓我們了解幕後發生了什麼。 Lombok 建立一個名為$LOCK或$lock的ReentrantLock類型的新字段,這取決於我們是在靜態方法還是實例方法上使用鎖定。
然後,它將方法中提供的程式碼包裝到取得ReentrantLock的區塊中。最後,當我們退出該方法時,它會釋放鎖。
此外,使用@Locked註解進行註解的多個方法將共用同一個鎖定。如果我們需要不同的鎖,我們可以建立一個ReentrantLock實例變數並將其名稱作為參數傳遞給@Locked註解。
讓我們測試這些方法以確保它們正常工作:
@Test
void givenCounter_whenIncrementCalledMultipleTimes_thenReturnCorrectResult() throws InterruptedException {
Counter counter = new Counter();
Thread.Builder builder = Thread.ofVirtual().name("worker-", 0);
Runnable task = counter::increment;
Thread t1 = builder.start(task);
t1.join();
Thread t2 = builder.start(task);
t2.join();
assertEquals(2, counter.get());
}
5.1. @Locked.Read和@Locked.Write註解
我們可以使用@Locked.Read和@Locked.Write註解來取代ReentrantReadWriteLock 。
顧名思義,在讀鎖上加@Locked.Read鎖的方法,而在寫鎖上加@Locked.Write鎖的方法。
讓我們修改increment()和get()方法中提供的程式碼並使用@Locked.Write和@Locked.Read註解:
@Locked.Write
public void increment() {
counter++;
}
@Locked.Read
public int get() {
return counter;
}
提醒一下,為讀取操作和寫入操作設定單獨的鎖定可以提高效能,尤其是在操作繁重時。
值得注意的是, Lombok 建立的ReentrantReadWriteLock欄位的名稱與@Locked註解產生的名稱相同。因此,如果我們想在同一個類別中使用這兩個註釋,我們需要為其中一個鎖指定一個自訂名稱。
6. @Locked和@Synchronized的差別
除了@Locked註解之外,Lombok還提供了類似的@Synchronized註解。這兩個註解都是為了確保線程安全。讓我們找出它們之間的差異。
雖然@Locked註解是ReentrantLock的替代品,但@Synchonized註解卻取代了synchronized修飾符。就像關鍵字一樣,我們只能在靜態或實例方法上使用它。然而,當synchronized鎖定this時,註解鎖定由 Lombok 建立的特定欄位。
另外,在使用虛擬線程時建議使用@Locked註解,而在相同情況下使用@Synchronized可能會導致效能問題。
七、結論
在這篇文章中,我們學習如何使用Lombok的@Locked註解。
綜上所述,Lombok引入註解是為了更好的支援虛擬線程。它代表ReentrantLock物件的替代。或者,我們了解如何使用@Lock.Read和@Lock.Write註解來指定讀鎖和寫鎖,而不是使用通用的鎖。最後,我們強調了@Locked和@Synchronized註釋之間的幾個區別。
與往常一樣,本文的源代碼可在 GitHub 上取得。