1. Overview

In this tutorial, we’ll explore approaches for executing a method only once. This is beneficial in several scenarios. For example, a method that initializes a singleton instance or a method that performs a one-time setup operation.

We’ll explore various techniques to ensure that a method is called only once. These techniques include using a boolean variable and the synchronized keyword, AtomicBoolean, and static initialization blocks. Additionally, certain unit testing frameworks such as JUnit and TestNG provide annotations that can help execute a method only once.

2. Using Boolean With Synchronized

Our first approach is to use a boolean flag in combination with the synchronized keyword. Let’s see how to implement it:

class SynchronizedInitializer {

    static volatile boolean isInitialized = false;
    int callCount = 0;

    synchronized void initialize() {
        if (!isInitialized) {
            initializationLogic();
            isInitialized = true;
        }
    }

    private void initializationLogic() {
        callCount++;
    }
}

In this implementation, we initially set the isInitialized flag to false. When the initialize() method is called for the first time, it checks if the flag is false. If it is, the one-time initialization logic is executed, and the flag is set to true. Subsequent calls to the initialize() method will see that the flag is already true and will not execute the initialization logic.

Synchronization ensures that only one thread can execute the initialize method at a time. This prevents multiple threads from executing the initialization logic simultaneously and potentially causing race conditions. We need the volatile keyword to ensure that each thread reads the updated boolean value.

We can test that we executed the initialization only once with the following test:

void givenSynchronizedInitializer_whenRepeatedlyCallingInitialize_thenCallCountIsOne() {
    SynchronizedInitializer synchronizedInitializer = new SynchronizedInitializer();
    assertEquals(0, synchronizedInitializer.callCount);

    synchronizedInitializer.initialize();
    synchronizedInitializer.initialize();
    synchronizedInitializer.initialize();

    assertEquals(1, synchronizedInitializer.callCount);
}

First, we create an instance of the SynchronizedInitializer and assert that the callCount variable is 0. After calling the initialize() method several times, the callCount increases to 1.

3. Using AtomicBoolean

Another approach to executing a method only once is using an atomic variable of type AtomicBoolean. Let’s see an implementation example:

class AtomicBooleanInitializer {

    AtomicBoolean isInitialized = new AtomicBoolean(false);
    int callCount = 0;

    void initialize() {
        if (isInitialized.compareAndSet(false, true)) {
            initializationLogic();
        }
    }

    private void initializationLogic() {
        callCount++;
    }
}

In this implementation, the isInitialized variable is initially set to false using the AtomicBoolean constructor. When we call the initialize() method for the first time, we call the compareAndSet() method with the expected value of false and the new value of true. If the current value of isInitialized is false, the method will set it to true and return true. Subsequent calls to the initialize() method will see that the isInitialized variable is already true and won’t execute the initialization logic.

Using AtomicBoolean ensures that the compareAndSet() method is an atomic operation, which means that only one thread can modify the value of isInitialized at a time. This prevents race conditions and ensures that the initialize() method is thread-safe.

We can verify that we executed the initializationLogic() method of the AtomicBooleanInitializer only once with the following test:

void givenAtomicBooleanInitializer_whenRepeatedlyCallingInitialize_thenCallCountIsOne() {
    AtomicBooleanInitializer atomicBooleanInitializer = new AtomicBooleanInitializer();
    assertEquals(0, atomicBooleanInitializer.callCount);

    atomicBooleanInitializer.initialize();
    atomicBooleanInitializer.initialize();
    atomicBooleanInitializer.initialize();

    assertEquals(1, atomicBooleanInitializer.callCount);
}

This test is very similar to the one we saw earlier.

4. Using Static Initialization

Static Initialization is another approach to execute a method only once:

final class StaticInitializer {

    public static int CALL_COUNT = 0;

    static {
        initializationLogic();
    }

    private static void initializationLogic() {
        CALL_COUNT++;
    }
}

The static initialization block is executed only once during class loading. It doesn’t require additional locking.

We can test that we called the initialization method of the StaticInitializer only once with the following tests:

void whenLoadingStaticInitializer_thenCallCountIsOne() {
    assertEquals(1, StaticInitializer.CALL_COUNT);
}

Because the static initialization block is already called during class loading, the CALL_COUNT variable is already set to 1.

void whenInitializingStaticInitializer_thenCallCountStaysOne() {
    StaticInitializer staticInitializer = new StaticInitializer();
    assertEquals(1, StaticInitializer.CALL_COUNT);
}

When creating a new instance of the StaticInitializer, the CALL_COUNT is still 1. We cannot call the static initialization block another time.

5. Using Unit Testing Frameworks

JUnit and TestNG provide annotations to run setup methods only once. In JUnit, the @BeforeAll annotation is used, and with TestNG or older JUnit versions, we can use the @BeforeClass annotations to execute a method only once.

Here’s an example of such a JUnit setup method:

@BeforeAll
static void setup() {
    log.info("@BeforeAll - executes once before all test methods in this class");
}

6. Conclusion

In this article, we learned different approaches to how we can ensure that we execute a method only once. We need this in different scenarios, e.g., initializing a database connection.

The approaches we saw make use of locking, AtomicBoolean, and static initializers. It’s also possible to use unit testing frameworks to run methods only once.

As always, the implementation of all these examples can be found over on GitHub.

Course – LS (cat=Java)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.