2013-11-21 8 views
7

Java Concurrency in Practice(106ページ)で、「Memoizer3は、複合アクション(put-if-absent)がバッキングマップで実行できないため、問題[2つのスレッドがnullを参照して高価な計算を開始する]ロックを使用してアトミックにする必要があります。私はロックを使ってアトミックにすることができないと言う理由を理解していません。元のコードは次のとおりです。Java Concurrency In Practicesリスティング5.18をロックで原子的に実行できないのはなぜですか?

package net.jcip.examples; 

import java.util.*; 
import java.util.concurrent.*; 

/** 
* Memoizer3 
* <p/> 
* Memoizing wrapper using FutureTask 
* 
* @author Brian Goetz and Tim Peierls 
*/ 
public class Memoizer3 <A, V> implements Computable<A, V> { 
    private final Map<A, Future<V>> cache 
     = new ConcurrentHashMap<A, Future<V>>(); 
    private final Computable<A, V> c; 

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

    public V compute(final A arg) throws InterruptedException { 
     Future<V> f = cache.get(arg); 
     if (f == null) { 
      Callable<V> eval = new Callable<V>() { 
       public V call() throws InterruptedException { 
        return c.compute(arg); 
       } 
      }; 
      FutureTask<V> ft = new FutureTask<V>(eval); 
      f = ft; 
      cache.put(arg, ft); 
      ft.run(); // call to c.compute happens here 
     } 
     try { 
      return f.get(); 
     } catch (ExecutionException e) { 
      throw LaunderThrowable.launderThrowable(e.getCause()); 
     } 
    } 
} 

なぜこの作品のようなものはありませんか?当然の

... 
public V compute(final A arg) throws InterruptedException { 
    Future<V> f = null; 
    FutureTask<V> ft = null; 
    synchronized(this){ 
     f = cache.get(arg); 
     if (f == null) { 
      Callable<V> eval = new Callable<V>() { 
       public V call() throws InterruptedException { 
        return c.compute(arg); 
       } 
      }; 
      ft = new FutureTask<V>(eval); 
      f = ft; 
      cache.put(arg, ft);     
     } 
    } 
    if (f==ft) ft.run(); // call to c.compute happens here 
    ... 

答えて

1

それは、最も原始的なケースを想像し、ロックを使用することにより、原子行うことができます:あなたはあなたの全体の機能の周りの世界のロックを持っている、そしてすべてのものは、シングルスレッド化され、スレッドセーフです。私は彼らが何か他のものを意味するか、一般的な誤解があったと仮定します。

コードがあっても、このようなのConcurrentHashMapのputIfAbsent法を使用することによって改善することができる:

public V compute(final A arg) throws InterruptedException { 
    Future<V> f = cache.get(arg); 
    if (f == null) { 
    final Callable<V> eval = new Callable<V>() { 
     public V call() throws InterruptedException { 
     return c.compute(arg); 
     } 
    }; 
    final FutureTask<V> ft = new FutureTask<V>(eval); 
    final Future<V> previousF = cache.putIfAbsent(arg, ft); 
    if (previousF == null) { 
     f = ft; 
     ft.run(); 
    } else { 
     f = previousF; // someone else will do the compute 
    } 
    } 
    return f.get(); 
} 

f、最後にで、間に追加された前回値又は初期値となるいずれかCallableの余分な作成の潜在的なコストですが、計算の呼び出しは複数回実行されません。

+0

ええ、著者はテキストにputIfAbsentソリューションを提供しています。 – PeteyPabPro

関連する問題