介紹
- Memoization適用于沒有參數(shù)的函數(shù)(Supplier)
- 當(dāng)我們想要執(zhí)行memoized方法時杏糙,我們可以簡單地調(diào)用返回的Supplier的get方法候衍,根據(jù)方法的返回值是否存在于內(nèi)存中拌屏,get方法將返回內(nèi)存中的值或執(zhí)行memoized方法并將返回值傳遞給調(diào)用方稀火。
- 在Suppliers類中有兩種memoization的方法:memoize和memoizeWithExpiration胧洒。
Supplier Memoization Without Eviction
我們可以使用supplier的memoize方法并指定委托的supplier作為方法引用:
Supplier<String> memoizedSupplier = Suppliers.memoize(
CostlySupplier::generateBigNumber);
由于我們沒有指定驅(qū)逐政策,一旦調(diào)用了get方法,返回的值將在Java應(yīng)用程序仍在運(yùn)行時保留在內(nèi)存中滚婉。在初始調(diào)用之后進(jìn)行的任何調(diào)用都將返回memoized值图筹。
Supplier Memoization with Eviction by Time-To-Live (TTL)
假設(shè)我們只想在備忘錄中保留供應(yīng)商返回的值一段時間。
我們可以使用供應(yīng)商的memoizeWithExpiration方法让腹,并指定過期時間及其相應(yīng)的時間單位(例如远剩,秒,分鐘)骇窍,以及委托的supplier:
Supplier<String> memoizedSupplier = Suppliers.memoizeWithExpiration(
CostlySupplier::generateBigNumber, 5, TimeUnit.SECONDS);
在指定的時間過去(5秒)后瓜晤,緩存將從內(nèi)存中退出Supplier的返回值,隨后對get方法的任何調(diào)用都將重新執(zhí)行g(shù)enerateBigNumber腹纳。
例子
public class CostlySupplier {
private static BigInteger generateBigNumber() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {}
return new BigInteger("12345");
}
}
@Test
public void givenMemoizedSupplier_whenGet_thenSubsequentGetsAreFast() {
Supplier<BigInteger> memoizedSupplier;
memoizedSupplier = Suppliers.memoize(CostlySupplier::generateBigNumber);
BigInteger expectedValue = new BigInteger("12345");
assertSupplierGetExecutionResultAndDuration(
memoizedSupplier, expectedValue, 2000D);
assertSupplierGetExecutionResultAndDuration(
memoizedSupplier, expectedValue, 0D);
assertSupplierGetExecutionResultAndDuration(
memoizedSupplier, expectedValue, 0D);
}
private <T> void assertSupplierGetExecutionResultAndDuration(
Supplier<T> supplier, T expectedValue, double expectedDurationInMs) {
Instant start = Instant.now();
T value = supplier.get();
Long durationInMs = Duration.between(start, Instant.now()).toMillis();
double marginOfErrorInMs = 100D;
assertThat(value, is(equalTo(expectedValue)));
assertThat(
durationInMs.doubleValue(),
is(closeTo(expectedDurationInMs, marginOfErrorInMs)));
}
原理
Suppliers#memoize
public static <T> Supplier<T> memoize(Supplier<T> delegate) {
return (delegate instanceof MemoizingSupplier)
? delegate
: new MemoizingSupplier<T>(Preconditions.checkNotNull(delegate));
}
看下 MemoizingSupplier類
@VisibleForTesting
static class MemoizingSupplier<T> implements Supplier<T>, Serializable {
final Supplier<T> delegate;
transient volatile boolean initialized;
// 讀取和寫入保證了讀取可見的寫入值
// value不需要使用volatile
transient T value;
MemoizingSupplier(Supplier<T> delegate) {
this.delegate = delegate;
}
@Override public T get() {
// 重點在這里 Double Checked Locking的變體
if (!initialized) {
synchronized (this) {
if (!initialized) {
T t = delegate.get();
value = t;
initialized = true;
return t;
}
}
}
return value;
}
@Override public String toString() {
return "Suppliers.memoize(" + delegate + ")";
}
private static final long serialVersionUID = 0;
}
Suppliers#memoizeWithExpiration
public static <T> Supplier<T> memoizeWithExpiration(
Supplier<T> delegate, long duration, TimeUnit unit) {
return new ExpiringMemoizingSupplier<T>(delegate, duration, unit);
}
@VisibleForTesting static class ExpiringMemoizingSupplier<T>
implements Supplier<T>, Serializable {
final Supplier<T> delegate;
final long durationNanos;
transient volatile T value;
// The special value 0 means "not yet initialized".
transient volatile long expirationNanos;
ExpiringMemoizingSupplier(
Supplier<T> delegate, long duration, TimeUnit unit) {
this.delegate = Preconditions.checkNotNull(delegate);
this.durationNanos = unit.toNanos(duration);
Preconditions.checkArgument(duration > 0);
}
@Override public T get() {
// Another variant of Double Checked Locking.
//
我們使用兩個易失性讀取痢掠。通過將我們的字段放入持有者類,
額外的內(nèi)存消耗和間接比額外的易失性讀取更昂貴嘲恍。
long nanos = expirationNanos;
long now = Platform.systemNanoTime();
if (nanos == 0 || now - nanos >= 0) {
synchronized (this) {
if (nanos == expirationNanos) { // recheck for lost race
T t = delegate.get();
value = t;
nanos = now + durationNanos;
expirationNanos = (nanos == 0) ? 1 : nanos;
return t;
}
}
}
return value;
}
@Override public String toString() {
// This is a little strange if the unit the user provided was not NANOS,
// but we don't want to store the unit just for toString
return "Suppliers.memoizeWithExpiration(" + delegate + ", " +
durationNanos + ", NANOS)";
}
private static final long serialVersionUID = 0;
}