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
}
}