Java並發,看到了,就記錄下唄


在這篇博客中,主要把之前看的書的內容記錄一下,個人感覺還是可以的,原題是這樣的:開發一個高效的緩存。這里指的是單機.

首先我來看當前的一個版本

1 public interface Computable<T, R> {
2     R compute(T input) throws InterruptedException;
3 }
 1 public class Memoizer1<T,R> implements  Computable<T,R>{
 2 
 3     private  final Map<T,R> cache = new HashMap<>();
 4 
 5     private  final Computable<T,R> computable;
 6 
 7     public Memoizer1(Computable<T, R> computable) {
 8         this.computable = computable;
 9     }
10 
11     public synchronized R compute(T input) throws InterruptedException {
12         R result= cache.get(input);
13         if(result ==null){
14             result = computable.compute(input);
15             cache.put(input,result);
16         }
17         return result;
18     }
19 }

     在該版本中利用HashMap來保存之前計算的結果,compute方法首先檢查緩存中是否有結果,沒有則計算,把其結果放入緩存並且返回。大家都知道HashMap不是線程安全的,因此要確保多個線程同時訪問的時,Memoizer1采用把對整個方法compute進行同步,這樣的結果導致調用該方法被串行化,如果compute的執行時間比較長,那么后面的線程需要等待更長的時間,結果可能比不用緩存更加糟糕。

     接着我們對該版本進行進一步的優化,把HashMap改為ConcurrentHashMap,因為ConcurrentHashMao是線程安全的,因此在訪問底層的Map的時候不需要進行同步。因此避免了在對compute方法進行同步帶來的串行性。

 1 public class Memoizer2<T,R> implements  Computable<T,R>{
 2 
 3     private  final Map<T,R> cache = new ConcurrentHashMap<>();
 4 
 5     private  final Computable<T,R> computable;
 6 
 7     public Memoizer2(Computable<T, R> computable) {
 8         this.computable = computable;
 9     }
10 
11     public R compute(T input) throws InterruptedException {
12         R result= cache.get(input);
13         if(result ==null){
14             result = computable.compute(input);
15             cache.put(input,result);
16         }
17         return result;
18     }
19 }

 

      在該版本也存在一些不足,當兩個線程同時調用compute時存在一個漏洞,可能會導致計算相同的值。緩存的目的是避免相同的數據被多次計算。我們知道FutureTask 表示一個計算過程,這個過程可能已經完成,也可能正在進行。如果FutureTask結果可用,調用FutureTask.get()將立即得到結果,否則它會一直阻塞,知道計算結果出來再返回。

 1 public class Memoizer3<T,R> implements  Computable<T,R>{
 2 
 3     private  final Map<T,Future> cache = new ConcurrentHashMap<>();
 4 
 5     private  final Computable<T,R> computable;
 6 
 7     public Memoizer3(Computable<T, R> computable) {
 8         this.computable = computable;
 9     }
10 
11     public R compute(final T input) throws InterruptedException {
12         Future<R> future = cache.get(input);
13         if (future==null){
14             Callable<R> callable = new Callable<R>() {
15                 @Override
16                 public R call() throws Exception {
17                     return computable.compute(input);
18                 }
19             };
20             FutureTask futureTask = new FutureTask(callable);
21             future=futureTask;
22             cache.put(input,future);
23             futureTask.run();
24         }
25         try{
26             return future.get();
27         } catch (ExecutionException e) {
28             throw  new RuntimeException(e);
29         }
30     }
31 }

      在第三個版本Memoizer3的實現幾乎是完美的,它表現出非常好的並發性,如果結果已經計算出來則直接返回,如果其他線程正在計算該結果,那么新到的線程將一直等待這個結果被計算出來。它只有一個缺陷,即仍然存在兩個線程計算出相同值的漏洞。

但是這個概率將遠遠小於第二個版本。由於compute方法中的if代碼仍然是非原子的“先檢查后執行”操作。因此兩個線程仍然有可能在同一時間內調用compute來計算相同的值。在該版本中存在該問題的原因是,復合操作在底層Map對象上執行,而這個對象無法通過加鎖來確保原子性。那么接着把Map.put()改為Map.putIfAbsent()即可。

 1 public class Memoizer4<T,R> implements  Computable<T,R>{
 2 
 3     private  final ConcurrentMap<T,Future> cache = new ConcurrentHashMap<>();
 4 
 5     private  final Computable<T,R> computable;
 6 
 7     public Memoizer4(Computable<T, R> computable) {
 8         this.computable = computable;
 9     }
10 
11     public R compute(final T input) throws InterruptedException {
12         while (true){
13             Future<R> future = cache.get(input);
14             if (future==null){
15                 Callable<R> callable = new Callable<R>() {
16                     @Override
17                     public R call() throws InterruptedException {
18                         return computable.compute(input);
19                     }
20                 };
21                 FutureTask futureTask = new FutureTask(callable);
22                 future= cache.putIfAbsent(input, futureTask);//如果原先不存在,返回null
23                 if(future==null){
24                     future=futureTask;
25                     futureTask.run();
26                 }
27             }
28             try{
29                 return future.get();
30             } catch (CancellationException e) {
31                 cache.remove(input,future);
32             } catch (ExecutionException e) {
33                 throw new RuntimeException(e);
34             }
35         }
36     }

在該版本中應該完美了解決了原先提出的問題。但是還是存在如下問題:沒有解決緩存預期的問題,沒有解決緩存清理問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM