在 Java 中只執行一次方法
一、概述
在本教程中,我們將探討僅執行一次方法的方法。這在多種情況下是有益的。例如,初始化單例實例的方法或執行一次性設置操作的方法。
我們將探索各種技術來確保一個方法只被調用一次。這些技術包括使用布爾變量和synchronized關鍵字、 AtomicBoolean和static初始化塊。此外,某些單元測試框架(例如 JUnit 和 TestNG)提供了可以幫助僅執行一次方法的註釋。
2. 同步使用布爾值
我們的第一種方法是結合使用布爾標誌和synchronized關鍵字。讓我們看看如何實現它:
class SynchronizedInitializer {
static volatile boolean isInitialized = false;
int callCount = 0;
synchronized void initialize() {
if (!isInitialized) {
initializationLogic();
isInitialized = true;
}
}
private void initializationLogic() {
callCount++;
}
}
在此實現中,我們最初將isInitialized標誌設置為false 。首次調用initialize()方法時,它會檢查標誌是否為false 。如果是,則執行一次性初始化邏輯,並將標誌設置為true 。對initialize()方法的後續調用將看到標誌已經為true ,並且不會執行初始化邏輯。
同步化 確保一次只有一個線程可以執行初始化方法。這可以防止多個線程同時執行初始化邏輯並可能導致競爭條件。我們需要volatile關鍵字來確保每個線程讀取更新的布爾值。
我們可以通過以下測試來測試我們是否只執行了一次初始化:
void givenSynchronizedInitializer_whenRepeatedlyCallingInitialize_thenCallCountIsOne() {
SynchronizedInitializer synchronizedInitializer = new SynchronizedInitializer();
assertEquals(0, synchronizedInitializer.callCount);
synchronizedInitializer.initialize();
synchronizedInitializer.initialize();
synchronizedInitializer.initialize();
assertEquals(1, synchronizedInitializer.callCount);
}
首先,我們創建SynchronizedInitializer的實例並斷言callCount變量為0 。多次調用initialize()方法後, callCount增加到1 。
3.使用AtomicBoolean
另一種只執行一次方法的方法是使用AtomicBoolean類型的原子變量。讓我們看一個實現示例:
class AtomicBooleanInitializer {
AtomicBoolean isInitialized = new AtomicBoolean(false);
int callCount = 0;
void initialize() {
if (isInitialized.compareAndSet(false, true)) {
initializationLogic();
}
}
private void initializationLogic() {
callCount++;
}
}
在此實現中, isInitialized變量最初使用AtomicBoolean構造函數設置為false 。當我們第一次調用initialize()方法時,我們調用compareAndSet()方法時使用期望值false和新值true 。如果isInitialized的當前值為false ,該方法會將其設置為true並返回true 。對initialize()方法的後續調用將看到isInitialized變量已經為true ,並且不會執行初始化邏輯。
使用AtomicBoolean確保compareAndSet()方法是一個原子操作,這意味著一次只有一個線程可以修改isInitialized的值。這可以防止競爭條件並確保initialize()方法是線程安全的。
我們可以通過以下測試驗證我們是否只執行了一次AtomicBooleanInitializer的initializationLogic()方法:
void givenAtomicBooleanInitializer_whenRepeatedlyCallingInitialize_thenCallCountIsOne() {
AtomicBooleanInitializer atomicBooleanInitializer = new AtomicBooleanInitializer();
assertEquals(0, atomicBooleanInitializer.callCount);
atomicBooleanInitializer.initialize();
atomicBooleanInitializer.initialize();
atomicBooleanInitializer.initialize();
assertEquals(1, atomicBooleanInitializer.callCount);
}
這個測試與我們之前看到的非常相似。
4.使用靜態初始化
靜態初始化是另一種只執行一次方法的方法:
final class StaticInitializer {
public static int CALL_COUNT = 0;
static {
initializationLogic();
}
private static void initializationLogic() {
CALL_COUNT++;
}
}
static初始化塊在類加載期間只執行一次。它不需要額外的鎖定。
我們可以通過以下測試來測試我們只調用了一次StaticInitializer的初始化方法:
void whenLoadingStaticInitializer_thenCallCountIsOne() {
assertEquals(1, StaticInitializer.CALL_COUNT);
}
因為在類加載期間已經調用了static初始化塊,所以CALL_COUNT變量已經設置為1 。
void whenInitializingStaticInitializer_thenCallCountStaysOne() {
StaticInitializer staticInitializer = new StaticInitializer();
assertEquals(1, StaticInitializer.CALL_COUNT);
}
創建StaticInitializer的新實例時, CALL_COUNT仍為1 。我們不能再調用static初始化塊。
5. 使用單元測試框架
JUnit 和 TestNG 提供註釋以僅運行一次設置方法。在 JUnit 中,使用@BeforeAll註釋,對於 TestNG 或更舊的 JUnit 版本,我們可以使用@BeforeClass註釋來僅執行一次方法。
下面是此類 JUnit 設置方法的示例:
@BeforeAll
static void setup() {
log.info("@BeforeAll - executes once before all test methods in this class");
}
六,結論
在本文中,我們了解瞭如何確保只執行一次方法的不同方法。我們在不同的場景中需要它,例如,初始化數據庫連接。
我們看到的方法使用鎖定、 AtomicBoolean和static初始化器。也可以使用單元測試框架只運行一次方法。
與往常一樣,所有這些示例的實現都可以在 GitHub 上找到。