在 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 上找到。