import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.ThreadSafe;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.function.Supplier;
/**
* Created by zhaoyy on 2017/7/17.
*/
@ThreadSafe
public final class SingletonCache<T extends Serializable> implements Supplier<T> {
private static final AtomicIntegerFieldUpdater<SingletonCache> STATE_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(SingletonCache.class, "state");
private static final AtomicIntegerFieldUpdater<SingletonCache> RETRY_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(SingletonCache.class, "retry");
private static final Logger logger = LoggerFactory.getLogger(SingletonCache.class);
private static final int NOT_LOADING = 0;
private static final int LOADING = 1;
//null is NOT permitted
private final SingletonLoader<T> loader;
private final long expiredTimeMills;
private final int maxRetry;
private volatile int state = NOT_LOADING;
private volatile int retry = 0;
private volatile T value = null;
private volatile long lastUpdateTime = -1;
private SingletonCache(SingletonLoader<T> loader, long expiredTimeMills, T defaultValue, int maxRetry) {
this.loader = loader;
this.expiredTimeMills = expiredTimeMills;
this.value = defaultValue;
this.maxRetry = maxRetry;
}
public static <T extends Serializable> Builder<T> builder() {
return new Builder<>();
}
@Nonnull
@Override
public T get() {
if (value == null) {
synchronized (this) {
if (value == null) {
state = LOADING;
updateValueSync();
}
}
} else {
if ((System.currentTimeMillis() - lastUpdateTime > expiredTimeMills)
&& STATE_UPDATER.compareAndSet(this, NOT_LOADING, LOADING)) {
updateValueAsync();
}
}
return value;
}
private void updateValueSync() {
final T newValue;
try {
newValue = loader.load(value);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
if (newValue == null)
throw new NullPointerException("null value is not permitted!");
updateValueAndReleaseLock(newValue);
}
private void updateValueAndReleaseLock(T value) {
this.value = value;
lastUpdateTime = System.currentTimeMillis();
retry = 0;
state = NOT_LOADING;
}
private void updateValueAsync() {
Thread thread = new Thread(this::loadValueRejectNull, "singleton-cache-async-runner");
thread.start();
}
private void loadValueRejectNull() {
final T newValue;
try {
newValue = loader.load(this.value);
} catch (Exception e) {
logger.error(e.getMessage(), e);
updateRetryUntilMaxRetry();
return;
}
if (newValue == null) {
updateRetryUntilMaxRetry();
return;
}
updateValueAndReleaseLock(newValue);
}
private void updateRetryUntilMaxRetry() {
assert state == LOADING;
if (RETRY_UPDATER.getAndAdd(this, 1) < maxRetry) {
state = NOT_LOADING;
logger.warn("current retry:{}", retry);
return;
}
logger.warn("retry over max times:{}, further update will not execute.", maxRetry);
}
@SuppressWarnings("WeakerAccess")
public static final class Builder<T extends Serializable> {
private long expiredTimeMills = -1;
private T defaultValue = null;
private SingletonLoader<T> loader = null;
private int maxRetry = 5;
public Builder<T> expiredAfter(long duration, TimeUnit unit) {
Preconditions.checkArgument(duration >= 0 && unit != null);
this.expiredTimeMills = unit.toMillis(duration);
return this;
}
public Builder<T> defaultValue(@Nonnull T defaultValue) {
this.defaultValue = defaultValue;
return this;
}
public Builder<T> loader(@Nonnull SingletonLoader<T> loader) {
this.loader = loader;
return this;
}
public Builder<T> maxRetry(int maxRetry) {
this.maxRetry = maxRetry;
return this;
}
public SingletonCache<T> build() {
Preconditions.checkArgument(expiredTimeMills >= 0, "expiredTimeMills < 0!");
Preconditions.checkArgument(loader != null, "null loader!");
Preconditions.checkArgument(maxRetry > 0, "max retry must be a positive number.");
return new SingletonCache<>(loader, expiredTimeMills, defaultValue, maxRetry);
}
}
}
import javax.annotation.Nullable;
import java.io.Serializable;
/**
* Created by zhaoyy on 2017/10/27.
*/
@FunctionalInterface
public interface SingletonLoader<T extends Serializable> {
//T is expected as immutable or effectively immutable
T load(@Nullable T oldValue) throws Exception;
}