Lombok @Locked指南

banq發表於2024-10-16


在本文中,我們學習瞭如何使用Lombok的@Locked註解。

Lombok 引入了該註釋以更好地支援虛擬執行緒。它代表了ReentrantLock物件的替代。我們看到如何使用@Lock.Read和@Lock.Write註釋來指定讀寫鎖,而不是使用通用鎖。最後,我們重點介紹了 @Locked和@Synchronized註釋之間的幾個區別。

Project Lombok是一個 Java 庫,它提供了各種註釋,我們可以使用它們來生成標準方法和功能,從而減少樣板程式碼。例如,我們可以使用 Lombok 生成 getter 和 setter、建構函式,甚至在程式碼中引入設計模式,例如Builder 模式。

在本教程中,我們將學習如何使用Lombok 版本 1.18.32 中引入的@Locked註釋。

為什麼要使用@Locked註解?
首先,讓我們瞭解@Locked註釋的必要性。

Java 21引入了虛擬執行緒,以簡化併發應用程式的實現、維護和除錯。它們與標準執行緒的區別在於,它們由 JVM 而不是作業系統管理。因此,它們的分配不需要系統呼叫,也不依賴於作業系統的上下文切換。

但是,我們應該意識到虛擬執行緒可能帶來的潛在效能問題。例如,當阻塞操作在同步塊或方法內執行時,它仍然會阻塞作業系統的執行緒。這種情況稱為鎖定。另一方面,如果阻塞操作在同步塊或方法之外,則不會引起任何問題。

此外,固定可能會對應用程式的效能產生負面影響,尤其是當阻塞操作被頻繁呼叫且持續時間較長時。值得注意的是,不頻繁且持續時間較短的阻塞操作不會引起此類問題。

解決固定問題的一種方法是用ReentrantLock替換synchronized。這就是新的 Lombok 註釋發揮作用的地方。

理解@Locked註解
簡單來說,@Locked註解是作為ReentrantLock的一個變體而建立的,其主要目的是為虛擬執行緒提供更好的支援。

此外,我們只能在靜態或例項方法上使用此註解。它將方法中提供的整個程式碼包裝到獲取鎖的塊中。此外,我們可以指定要用於鎖定的ReentrantLock型別的欄位。結果,Lombok 對該特定欄位執行鎖定。方法執行完成後,它將解鎖。

現在,讓我們看看@Locked註釋的實際作用。

依賴設定
讓我們在pom.xml中新增lombok依賴項:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.34</version>
    <scope>provided</scope>
</dependency>

值得注意的是,我們需要 1.18.32 或更高版本才能使用此註釋。

使用
讓我們使用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(<font>"worker-", 0);
    Runnable task = counter::increment;
    Thread t1 = builder.start(task);
    t1.join();
    Thread t2 = builder.start(task);
    t2.join();
    assertEquals(2, counter.get());
}

 @Locked.Read和@Locked.Write註釋
我們可以使用@Locked.Read和@Locked.Write註釋代替ReentrantReadWriteLock。

顧名思義,用@Locked.Read lock註釋的方法對讀鎖起作用,而用@Locked.Write lock註釋的方法對寫鎖起作用。

讓我們修改increment()和get()方法中提供的程式碼 並使用@Locked.Write和@Locked.Read註釋:

@Locked.Write
public void increment() {
    counter++;
}
@Locked.Read
public int get() {
    return counter;
}

提醒一下,對讀寫操作使用單獨的鎖可以提高效能,特別是在操作繁重時。

值得注意的是, Lombok 建立的ReentrantReadWriteLock欄位的名稱 與為@Locked註釋生成的欄位名稱相同。因此,如果我們想在同一個類中使用這兩個註釋,我們需要為其中一個鎖指定一個自定義名稱。

@Locked和@Synchronized之間的區別
除了@Locked註解外,Lombok還提供了類似的@Synchronized註解,這兩個註解的目的都是保證執行緒安全,下面我們來看一下它們的區別。

@Locked註釋是ReentrantLock的替代品,而@Synchonized註釋則取代了synchronized修飾符。就像關鍵字一樣,我們只能在靜態或例項方法上使用它。但是,synchronized鎖定this ,而註釋鎖定 Lombok 建立的特定欄位。

此外,使用虛擬執行緒時建議使用@Locked註解,而在相同情況下使用@Synchronized可能會導致效能問題。

 

相關文章