6

私はScalaと関数型プログラミングには比較的新しいので、不変なオブジェクトを使うとスレッドセーフの落とし穴を避けることができます。 1つのことはまだ私を悩ましています。スレッドセーフティを教えるために使用される古典的な例 - 共有カウンタです。スレッドセーフな共有カウンタを実装するための機能的方法

スレッドセーフカウンタ(この例ではリクエストカウンタ)を実装し、不変なオブジェクトと機能概念を使用して同期を完全に回避できるかどうかは疑問でした。

:ここに参照のためにそう

は、第1のカウンタ の古典可変バージョン(ちょうど例の簡略化のために、パブリックメンバ変数のためにすみません)

変更可能な、非スレッドセーフ版です

public class Servlet extends HttpServlet { public int requestCount = 0; @Override public void service(ServletRequest req, ServletResponse res) throws ... { requestCount++; //thread unsafe super.service(req, res); } } 

変更可能な、クラシック、スレッドセーフ版:(またはので、私は願っています...)

public class Servlet extends HttpServlet { 

    public volatile int requestCount = 0; 

    @Override 
    public void service(ServletRequest req, ServletResponse res) throws ... { 
    synchronized (this) { 
     requestCount++; 
    } 
    super.service(req, res); 
    } 
} 

不変なオブジェクトと揮発性変数を使用する方法がある場合は、同期なしでスレッドの安全性を実現する方法があるのだろうかと思っていました。

ここで私の素朴な試みでした。この考え方は、カウンタに不変なオブジェクトを持たせ、揮発性の変数を使ってその参照を置き換えるだけです。怪しげな気分だが、一発の価値がある。

ホルダー:

public class Incrementer { 
    private final int value; 
    public Incrementer(final int oldValue) { 
    this.value = oldValue + 1; 
    } 

    public Incrementer() { 
    this.value = 0; 
    } 

    public int getValue() { 
    return value; 
    } 
} 

修正サーブレット:

public class Servlet extends HttpServlet { 

    public volatile Incrementer incrementer = new Incrementer(); 

    @Override 
    public void service(ServletRequest req, ServletResponse res) throws ... { 
    incrementer = new Incrementer(incrementer.getValue()); 
    super.service(req, res); 
    } 
} 

私はこれは私がインクリメンタから読んでいるとしても、スレッドセーフされていない、となるかもしれない強い気持ちを持っています古い値(例えば、参照がすでに別のスレッドに置き換えられている場合など)。実際にスレッドセーフではない場合は、ロック/同期なしにこのような対抗シナリオを処理するための「機能的」な方法はまったくないのだろうかと思います。

だから私の質問(複数可)

  1. は、このスレッドは、万が一安全ですか?
  2. 「はい」の場合、なぜですか?
  3. そうでなければ、同期させずにそのようなカウンタを実装する方法はありますか?

上記の例のコードは、Javaでですがが、Scalaで返信もちろん

答えて

13

を歓迎している万が一安全このスレッドですか?

いいえ、すでに同期ブロックに不変オブジェクトを作成していない限り、これはスレッドセーフではありません。スレッド競合状態で破損した不変オブジェクトを作成する可能性があります。

そして同じ機能を実現するには、明示的な同期を避けるAtomicIntegerを使用できます。

public class Servlet extends HttpServlet { 

    public AtomicInteger incrementer = new AtomicInteger (0); 

    @Override 
    public void service(ServletRequest req, ServletResponse res) throws ... { 
    int newValue = incrementer.incrementAndGet(); 
    super.service(req, res); 
    } 
} 
+0

興味深いことに、AtomicIntegerは内部的に同期を使用しているので、私はそれを答えとして考えていませんでしたが、目標を達成するためにいくつかの他の方法(ネイティブコード)を使っているようです。 /grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/atomic/AtomicInteger.java –

4

immutablesのスレッドセーフは、このようになります。

val x = AtomicReference(Vector("salmon", "cod")) 

// Thread 1 
val y = x.get 
println(y(y.length-1)) 

// Thread 2 
x.getAndSet(x.get.tail) 

あなたはmutably働いていた場合は、痛んでスレッド2は、スレッド1のインデックスが失敗作ることができる可変リストは、変更持つように誘惑されるだろう。または、データをコピーする必要があります。これは、実用的なほど再利用するコレクションがない場合(およびベクトルが長くなった場合)、非常に高価になる可能性があります。または、データをアトミックに取得および/または取得する代わりに、両方のスレッドで大きなブロックを同期させる必要があります。

何とか同期する必要があります。古くなったデータのコピーを処理する必要があります。しかし、は誰がどのデータ構造のコピーを持っているかを把握しておかなければならないし、そのデータがあなたのもとから変更され、その場所の例外をスローする可能性があるため、みんな気に入ってください。

関連する問題