2010-12-07 2 views
1

私はこのクラスをシングルトンとして実装しています。私はスレッドの安全性が良くありません。 GenerateOrderIDクラスがスレッドセーフであることを確認したいより具体的には、orderCount変数を異なるオブジェクトで同時にインクリメントしてカウントをオフにすることはできません。このクラスはスレッドセーフですか?

public class OrderIDGenerator 

{ 

    private static readonly OrderIDGenerator instance = new OrderIDGenerator(); 
    private int orderCount; 


    private OrderIDGenerator() 
    { 
     orderCount = 1; 
    } 


    public static OrderIDGenerator Instance 
    { 
     get { return instance; } 
    } 

    public string GenerateOrderID() 
    { 
     return String.Format("{0:yyyyMMddHHmmss}{1}", DateTime.Now, orderCount++); 
    } 

} 

答えて

8

これはありません。ポストインクリメント操作はアトミックではありません。

はこれでGenerateOrderIDメソッドを置き換えます:あなたは、以下の変更を加える必要があります

public string GenerateOrderID() 
{ 
    return String.Format("{0:yyyyMMddHHmmss}{1}", 
         DateTime.Now, 
         Interlocked.Increment(ref orderCount)); 
} 

そしてInterlocked.Incrementがインクリメント値を返すために、代わりに1の0にorderCountを初期化します。 (換言すれば、Interlocked.Increment(ref foo)は、原子、従ってスレッドセーフであることを除いて++fooにあらゆる点で同じである。)Interlocked.Incrementスレッドを同期させるためにはるかに効率的lockの使用よりも

注意こと、しかしlockはまだ意志作業。 this questionを参照してください。

また、シングルトンは使用しないでください。

+0

あなたの精巧な答えをありがとう。特に好奇心が強いのですが、特にシングルトンを使用しないでください。 – bkarj

+0

@Behrooz:なぜならシングルトンは効果的に柔軟性を制限しているからです。 2つのデータベースの注文番号を処理するようにサービス/アプリケーションを拡張したふりをする。シングルトンはこれを不可能にするでしょう。代わりに、依存関係インジェクションや、渡されたいくつかの "コンテキスト"オブジェクトを使用して、 'OrderIDGenerator'インスタンスを参照する必要があります。 – cdhowie

1

私はorderCountをvolatileとマークし(コンパイラの最適化の前提を避けるため)、増分の周りにロックを使用します。揮発性はおそらく過剰ですが、傷つくことはありません。

public class OrderIDGenerator 
{ 

    private static readonly OrderIDGenerator instance = new OrderIDGenerator(); 
    private volatile int orderCount; 
    private static object syncRoot = new object(); 


    private OrderIDGenerator() 
    { 
     orderCount = 1; 
    } 


    public static OrderIDGenerator Instance 
    { 
     get { return instance; } 
    } 

    public string GenerateOrderID() 
    { 
     lock (syncRoot) 
      return String.Format("{0:yyyyMMddHHmmss}{1}", DateTime.Now, orderCount++); 
    } 
} 
+1

ロックは過大です。 Interlocked.Incrementは、このシナリオではより適切です。 – cdhowie

+0

ああ、もちろん。後世のために私の元の答えを残して(しかし、上記のcdhowieの答えに行く)。 –

0

あなたは、単にそれは小さな変更で、スレッドセーフでなければならないことができます

public class OrderIDGenerator { 
    private static readonly OrderIDGenerator instance = new OrderIDGenerator(); 
    private int orderCount; 
    private static readonly object _locker = new object(); 

    private OrderIDGenerator() { 
      orderCount = 1; 
    } 

    public static OrderIDGenerator Instance { 
      get { return instance; } 
    } 

    public string GenerateOrderID() { 
     string orderId = ""; 
     lock (_locker) { 
      orderID = String.Format("{0:yyyyMMddHHmmss}{1}", DateTime.Now, orderCount++); 
     } 
     return orderID; 
    } 
} 
3

このクラスはスレッドセーフではありません。

インクリメントはアトミックな操作ではありません。コンテキストスイッチがちょうど間違った時に起こることが完全に可能である:

Start with orderCount at 1. 
Two threads try to GenerateOrderID() at once: 

Thread 1    | Thread 2 
---------------------------------------------- 
read orderCount = 1 | 
        --> 
        | read orderCount = 1 
        | add: 1 + 1 
        | write orderCount = 2 
        <-- 
add: 1 + 1   | 
write orderCount = 2 | 
return timestamp1 | 
        --> 
        | return timestamp1 

あなたは今のご注文のカウントがオフにスローと重複注文IDを持っています。この問題を解決するには

、それへのアクセス中にorderCountロック:

public string GenerateOrderID() 
{ 
    lock(this){ 
     return String.Format("{0:yyyyMMddHHmmss}{1}", DateTime.Now, orderCount++); 
    } 
} 

文は一つだけの時にロックされている各オブジェクトに対して、一度にスレッドすることができます「ロック」。 1つのスレッドがGenerateOrderID()にある場合、完了するまでOrderIDGeneratorがロックされ、次のスレッドがロックを要求しようとすると、最初のスレッドが完全に完了するまで待機する必要があります。

lockステートメントhereの詳細を参照してください。

+0

注:CDHowieが正しいです。Interlocked.Increment(ref orderCount)は、lock文全体を使用するよりも優れています。 –

+0

+1スレッド不安問題の+1きれいな説明!おめでとう! – Lorenzo

関連する問題