構建高效且可伸縮的結果快取

壹頁書發表於2015-08-25
JAVA併發程式設計實踐
第85頁

環境 JDK1.6(貌似使用1.7編譯會有問題)

第一版 synchronized 控制併發

  1. import java.math.BigInteger;
  2. import java.util.HashMap;
  3. import java.util.Map;

  4. interface Computable<A, V> {
  5.     V compute(A arg) throws InterruptedException;
  6. }

  7. class ExpensiveFunction implements Computable<String, BigInteger> {
  8.     @Override
  9.     public BigInteger compute(String arg) throws InterruptedException {
  10.         return new BigInteger(arg);
  11.     }
  12. }

  13. class Memoizer1<A, V> implements Computable<A, V> {
  14.     private final Map<A, V> cache = new HashMap<A, V>();
  15.     private final Computable<A, V> c;

  16.     public Memoizer1(Computable<A, V> c) {
  17.         this.c = c;
  18.     }

  19.     @Override
  20.     public synchronized V compute(A arg) throws InterruptedException {
  21.         V result = cache.get(arg);
  22.         if (result == null) {
  23.             result = c.compute(arg);
  24.             cache.put(arg, result);
  25.         }
  26.         return result;

  27.     }
  28. }
ExpensiveFunction 模擬一個很費時間的操作.比如資料庫讀取。

這個版本問題很明顯,就是synchronized 限制了併發.



第二版 
ConcurrentHashMap替換HashMap

  1. import java.math.BigInteger;
  2. import java.util.Map;
  3. import java.util.concurrent.ConcurrentHashMap;

  4. interface Computable<A, V> {
  5.     V compute(A arg) throws InterruptedException;
  6. }

  7. class ExpensiveFunction implements Computable<String, BigInteger> {
  8.     @Override
  9.     public BigInteger compute(String arg) throws InterruptedException {
  10.         return new BigInteger(arg);
  11.     }
  12. }

  13. class Memoizer2<A, V> implements Computable<A, V> {
  14.     private final Map<A, V> cache = new ConcurrentHashMap<A, V>();
  15.     private final Computable<A, V> c;

  16.     public Memoizer2(Computable<A, V> c) {
  17.         this.c = c;
  18.     }

  19.     @Override
  20.     public V compute(A arg) throws InterruptedException {
  21.         V result = cache.get(arg);
  22.         if (result == null) {
  23.             result = c.compute(arg);
  24.             cache.put(arg, result);
  25.         }
  26.         return result;

  27.     }
  28. }
這個版本的問題是,在併發很高的情況下,一旦快取失效,大量的請求會擁塞資料庫的所有執行緒.造成資料庫的間歇性問題.

第三版 Future

  1. import java.math.BigInteger;
  2. import java.util.Map;
  3. import java.util.concurrent.Callable;
  4. import java.util.concurrent.ConcurrentHashMap;
  5. import java.util.concurrent.ExecutionException;
  6. import java.util.concurrent.Future;
  7. import java.util.concurrent.FutureTask;

  8. interface Computable<A, V> {
  9.     V compute(final A arg) throws InterruptedException, ExecutionException;
  10. }

  11. class ExpensiveFunction implements Computable<String, BigInteger> {
  12.     @Override
  13.     public BigInteger compute(String arg) throws InterruptedException, ExecutionException {
  14.         return new BigInteger(arg);
  15.     }
  16. }

  17. class Memoizer3<A, V> implements Computable<A, V> {
  18.     private final Map<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();
  19.     private final Computable<A, V> c;

  20.     public Memoizer3(Computable<A, V> c) {
  21.         this.c = c;
  22.     }

  23.     @Override
  24.     public V compute(final A arg) throws InterruptedException, ExecutionException {
  25.         Future<V> f = cache.get(arg);
  26.         if (f == null) {
  27.             Callable<V> eval = new Callable<V>() {

  28.                 @Override
  29.                 public V call() throws ExecutionException, InterruptedException {
  30.                     return c.compute(arg);
  31.                 }

  32.             };
  33.             FutureTask<V> ft = new FutureTask<V>(eval);
  34.             f = ft;
  35.             cache.put(arg, ft);
  36.             ft.run();
  37.         }
  38.         return f.get();
  39.     }
  40. }
這個版本的問題是
if(f==null)是非原子的"先檢查後執行",可能會有多個執行緒同時進入該段程式碼.但是相對於第二版,概率已經大大降低.

最終版本

  1. import java.math.BigInteger;
  2. import java.util.Map;
  3. import java.util.concurrent.Callable;
  4. import java.util.concurrent.ConcurrentHashMap;
  5. import java.util.concurrent.ExecutionException;
  6. import java.util.concurrent.Future;
  7. import java.util.concurrent.FutureTask;

  8. interface Computable<A, V> {
  9.     V compute(final A arg) throws InterruptedException;
  10. }

  11. class ExpensiveFunction implements Computable<String, BigInteger> {
  12.     @Override
  13.     public BigInteger compute(String arg) throws InterruptedException {
  14.         return new BigInteger(arg);
  15.     }
  16. }

  17. class Memoizer3<A, V> implements Computable<A, V> {
  18.     private final ConcurrentHashMap<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();
  19.     private final Computable<A, V> c;

  20.     public Memoizer3(Computable<A, V> c) {
  21.         this.c = c;
  22.     }

  23.     @Override
  24.     public V compute(final A arg) throws InterruptedException {
  25.         while (true) {
  26.             Future<V> f = cache.get(arg);
  27.             if (f == null) {
  28.                 Callable<V> eval = new Callable<V>() {
  29.                     public V call() throws InterruptedException {
  30.                         return c.compute(arg);
  31.                     }
  32.                 };

  33.                 FutureTask<V> ft = new FutureTask<V>(eval);
  34.                 f = cache.putIfAbsent(arg, ft);
  35.                 if (f == null) {
  36.                     f = ft;
  37.                     ft.run();
  38.                 }
  39.             }
  40.             try {
  41.                 return f.get();
  42.             } catch (ExecutionException e) {
  43.                 e.printStackTrace();
  44.             }
  45.         }
  46.     }
  47. }
這個版本還是有一些問題,比如沒有快取失效機制,並且異常的時候沒有將Future從Map中移除.
不過已經說明了很多的用法.

這個while迴圈感覺很精妙
一旦 return f.get()發生ExecutionException異常,程式碼不會退出,而是會再次執行.

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29254281/viewspace-1782226/,如需轉載,請註明出處,否則將追究法律責任。

相關文章