3
\$\begingroup\$

In my application there is a "config" object that is accessbile by multiple threads concurrently. It is a single object (singleton) injected by the DI in the "dependents". Until now, this (the "config") object was immutable which makes it inherently thread-safe. However, there is a new requirement that the "config" should be periodically "refreshed/updated". I wrote a wrapper (decorator) that refreshes itself (from a configProvider), and the refresh happens on the fly, when a method gets called (if the certian amount of time has passed) The wrapper should be thread safe and non-blocking.

Any (concurrency) issues with the following code?

package xyz;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Implementation of {@link IMyModuleConfig} that periodically refreshes itself from the provided
 * {@link IConfiguration}.
 * The refresh is done on-the-fly when some method is called, it is thread safe.
 *
 * @Threadsafe
 */
public class MyModuleRefreshableConfig implements IMyModuleConfig {

    private IMyModuleConfig configBean; // Decorated object, Immutable, non-final should be properly fenced 
    private long lastRefreshTime;  // time when we refreshed the configuration last, non-final should be properly fenced 

    private final IConfiguration configProvider; // refresh source
    private final long refreshInterval; // how often we refresh


    private final AtomicBoolean refreshConfigMutex; // if true, a thread is refreshing configuration right now

    public MyModuleRefreshableConfig(IConfiguration configProvider, long refreshInterval) {
        refreshConfigMutex = new AtomicBoolean(false);

        this.configBean = refreshConfig();
        this.lastRefreshTime = System.currentTimeMillis();

        // Do not re-arrange, the assignment to final field latter in the constructor serves as a "store fence"

        this.configProvider = configProvider;
        this.refreshInterval = refreshInterval;
    }

    @Override
    public boolean getMyBoolean() {
        refreshConfigIfNeeded(); // full fence
        return this.configBean.getMyBoolean();
    }

    @Override
    public int getTimeout() {
        refreshConfigIfNeeded(); // full fence
        return this.configBean.getTimeout();
    }

    private void refreshConfigIfNeeded() {
        boolean aquired = refreshConfigMutex.compareAndSet(false, true); // full fence
        if (aquired) { // critical section starts, if some thread is in just continue, no need to wait
            long cTime = System.currentTimeMillis();
            boolean shouldRefresh = (cTime - lastRefreshTime) > refreshInterval;
            if (shouldRefresh) {
                lastRefreshTime = cTime;
                configBean = refreshConfig();
            }
            refreshConfigMutex.set(false); // critical section ends, write fence
        }
    }

    private IMyModuleConfig refreshConfig() {
        // creates new immutable IMyModuleConfig with values provided by configProvider
    }
}
\$\endgroup\$
1
\$\begingroup\$

As long as you are 100% certain that all threads will use the same instance of MyModuleRefreshableConfig, this code should work. Just to be certain I would make the refreshConfigMutexstatic:

private static final AtomicBoolean refreshConfigMutex;

public MyModuleRefreshableConfig(IConfiguration configProvider, long refreshInterval) {
    synchronized {
         if (refreshConfigMutex == null) {
             refreshConfigMutex = new AtomicBoolean(false);
         }
    }
    ....
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.