Zero Burst Under Load

This singleton ships to a 32-core service. readConfig reads a properties file and never returns 0 for either key.

public final class RateLimiter {
    private static RateLimiter instance;   // note: not volatile
    private int burst;
    private long refillNanos;

    private RateLimiter() {
        this.burst = readConfig("burst");             // 200
        this.refillNanos = readConfig("refill") * 1_000_000L;
    }

    public static RateLimiter get() {
        if (instance == null) {                       // fast path, no lock
            synchronized (RateLimiter.class) {
                if (instance == null) {
                    instance = new RateLimiter();
                }
            }
        }
        return instance;
    }

    public int burst() { return burst; }
}

Roughly once a week, under a traffic spike, a request thread sees burst() return 0 and every caller is throttled. What is happening?

A

The inner null check is dead code once the outer one has passed, and it is the second check that opens the window between the test and the assignment.

B

A thread on the fast path can read a non-null instance whose fields still hold their default 0, because nothing orders the constructor's writes ahead of the write that publishes the reference. Declaring instance volatile fixes it on JDK 5 and later.

C

Two threads both pass the outer null check and both run the constructor, and the second assignment overwrites the first object while a third thread is still reading it.

D

synchronized (RateLimiter.class) locks the class object, which only excludes other static synchronized methods, so a thread calling the instance method burst() can enter the block concurrently.