一臺計算機的核心是CPU,它是計算機系統的運算和控制核心。因爲它處理運算速度快,因此基本都會給CPU配置一級緩存,當CPU要讀取一個數據時,首先從緩存中查詢,若是沒有在從內存或者磁盤塊中找。
一樣的,做爲一個服務器應用程序,爲了讓應用程序運行更快速,響應更給力,咱們會給它設置一些數據緩存,這樣能夠提升應用程序的吞吐量、縮短客戶端的響應時間。java
咱們從java最經常使用的方案開始——一個簡單的HashMap。緩存
public interface Computable<A, V> { V compute(A arg) throws InterruptedException; } public class ExpensiveFunction implements Computable<String, BigInteger> { @Override public BigInteger compute(String arg) throws InterruptedException { // after deep thought... return new BigInteger(arg); } }
Computable<A, V>接口描述了一個功能,輸入類型是A,輸出結果的類型是V。ExpensiveFunction實現了Computable。須要花比較長的時間來計算結果。因此咱們計劃把計算過的值都放進一個HashMap中,這樣下一次有同一個A值進來時,直接獲取A的計算結果。安全
public class Memoizer1<A, V> implements Computable<A, V> { private final Map<A, V> cache = new HashMap<A, V>(); private final Computable<A, V> c; public Memoizer1(Computable<A, V> c) { this.c = c; } @Override public synchronized V compute(A arg) throws InterruptedException { V result = cache.get(arg); if (result == null) { result = c.compute(arg); cache.put(arg, result); } return result; } }
Memoizer1實現了第一個版本,HashMap不是線程安全的,因此使用synchronzied關鍵字來保證線程安全,若是cache變量中有計算結果,直接從cache取,不須要再次計算,省下許多時間。但使用synchronzied使得一次只有一個線程可以執行compute。若是一個線程正在計算結果,那其餘調用compute的線程可能被阻塞很長時間,形成性能降低,這不是咱們但願經過緩存獲得的性能優化結果。性能優化
public class Memoizer2<A, V> implements Computable<A, V> { private final Map<A, V> cache = new ConcurrentHashMap<>(); private final Computable<A, V> c; public Memoizer2(Computable<A, V> c) { this.c = c; } @Override public V compute(A arg) throws InterruptedException { V result = cache.get(arg); if (result == null) { result = c.compute(arg); cache.put(arg, result); } return result; } }
Memoizer2用ConcurrentHashMap取代HashMap,改進了Memoizer1中那種糟糕的併發行爲。由於ConcurrentHashMap是線程安全的,因此不須要使用Synchronized關鍵字,而是使用內部hash桶的分段鎖機制。
Memoizer2與Memoizer1相比,毫無疑問具備了更好的併發性:多線程能夠真正併發訪問了。可是做爲高速緩存仍然存在缺陷:當兩個線程同時調用compute時,若是是計算同一個值,此時compute是須要很大的開銷的,在一個線程還在計算中時,其它線程不知道,因此可能會重複計算。而咱們但願的是:若是A線程正在計算arg,那B就不要重複計算arg,等A計算好,直接取arg對應的V就行了。服務器
public class Memoizer3<A, V> implements Computable<A, V> { private final Map<A, Future<V>> cache = new ConcurrentHashMap<>(); private final Computable<A, V> c; public Memoizer3(Computable<A, V> c) { this.c = c; } @Override public V compute(A arg) throws InterruptedException { Future<V> f = cache.get(arg); if (f == null) { Callable<V> eval = () -> { return c.compute(arg); }; FutureTask<V> ft = new FutureTask<>(eval); f = ft; cache.put(arg, ft); ft.run(); // 調用 c.compute發生在這裏 } try { return f.get(); } catch (ExecutionException e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } } }
Memoizer3爲緩存的值從新定義可存儲Map,用ConcurrentHashMap<A, Future
Memoizer3的實現近乎是完美的:它展現了很是好的併發性,能很快返回已經計算過的結果,若是新到的線程請求的是其它線程正在計算的結果,它也會耐心的等待。
Memoizer3只有一個問題,就是仍然存在這種可能性:2個線程同時計算arg,此時因爲compute中的if代碼塊是非原子性的複合操做,2個線程會同時進入到if代碼塊中,依舊會同時計算同一個arg。但ConcurrentHashMap中提供了一個原子化的putIfAbsent方法,能夠消除Memoizer3的隱患。
public class Memoizer<A, V> implements Computable<A, V> { private final Map<A, Future<V>> cache = new ConcurrentHashMap<>(); private final Computable<A, V> c; public Memoizer(Computable<A, V> c) { this.c = c; } @Override public V compute(A arg) throws InterruptedException { Future<V> f = cache.get(arg); if (f == null) { Callable<V> eval = () -> { return c.compute(arg); }; FutureTask<V> ft = new FutureTask<>(eval); f = ft; cache.putIfAbsent(arg, ft); ft.run(); // 調用 c.compute發生在這裏 } try { return f.get(); } catch (ExecutionException e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } } }
Memoizer能夠說是緩存的完美實現了:支持高併發,同一個參數計算也不會重複執行(多虧於ConcurrentHashMap的putIfAbsent原子化操做)。最終調用者經過調用Future.get(arg)方法獲取計算結果。併發