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