web-dev-qa-db-ja.com

同期ブロックによるJavaのスレッドセーフクラス

非常に単純なJava class MyClassがあるとします。

_public class MyClass {
   private int number;

    public MyClass(int number) {
        this.number = number;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}
_

スレッドセーフなJavaいくつかの状態を持つクラスを構築する方法は3つあります:

  1. 真に不変にする

    _public class MyClass {
       private final int number;
    
       public MyClass(int number) {
        this.number = number;
       }
    
       public int getNumber() {
        return number;
       }
    
    }
    _
  2. フィールドnumbervolatileを作成します。

    _public class MyClass {
       private volatile int number;
    
       public MyClass(int number) {
        this.number = number;
       }
    
       public int getNumber() {
           return number;
       }
    
       public void setNumber(int number) {
           this.number = number;
       }
    }
    _
  3. synchronizedブロックを使用します。 Java実際の並行性)の第4.3.5章で説明されているこのアプローチのクラシックバージョン。この本の正誤表に記載されている例にエラーがあるのは面白いことです。

    _public class MyClass {
       private int number;
    
       public MyClass(int number) {
           setNumber(number);
       }
    
       public synchronized int getNumber() {
           return number;
       }
    
       public synchronized void setNumber(int number) {
           this.number = number;
       }
    }
    _

議論の文脈に加えられるべきもう一つの事実があります。マルチスレッド環境では、JVMはsynchronizedブロックの外で命令を自由に並べ替えて、JVMで指定された論理シーケンスとhappens-before関係を維持できます。まだ適切に構築されていないオブジェクトを別のスレッドに公開する可能性があります。

3番目のケースに関していくつか質問があります。

  1. 次のコードと同等になりますか?

    _public class MyClass {
       private int number;
    
       public MyClass(int number) {
           synchronized (this){
               this.number = number;
           }
       }
    
       public synchronized int getNumber() {
           return number;
       }
    
       public synchronized void setNumber(int number) {
           this.number = number;
       }
    }
    _
  2. 3番目のケースでは並べ替えが禁止されますか、それともJVMが命令を並べ替えて、フィールドnumberにデフォルト値を指定してオブジェクトを公開することは可能ですか?

  3. 2番目の質問の答えが「はい」の場合、もう1つ質問があります。

    _ public class MyClass {
       private int number;
    
       public MyClass(int number) {
           synchronized (new Object()){
               this.number = number;
           }
       }
    
       public synchronized int getNumber() {
           return number;
       }
    
       public synchronized void setNumber(int number) {
           this.number = number;
       }
    }
    _

この奇妙に見えるsynchronized (new Object())は、並べ替えの影響を防ぐことになっています。うまくいきますか?

明確にするために、これらすべての例には実用的なアプリケーションはありません。私はマルチスレッドのニュアンスに興味があります。

19
wax

synchronized(new Object())は何もしません。同期は同期するオブジェクト上でのみ行われるためです。したがって、スレッドAがoneObjectで同期し、スレッドBがanotherObjectで同期する場合、それらの間で前に発生することはありません。そこに作成したnew Object()で他のスレッドが同期することはないという事実を知ることができるので、これは他のどのスレッド間でも前に発生を確立しません。

コンストラクターのsynchronziedに関して、オブジェクトが別のスレッドに安全に公開されている場合、それは必要ありません。そうでない場合は、おそらく現状のままで問題が発生しています。私は少し前に同時実行インタレストリストでこの質問をしたところ、 興味深いスレッドが生成されました です。特に this email を参照してください。これは、コンストラクターが同期されていても、安全な公開がない場合、別のスレッドがフィールドのデフォルト値を表示する可能性があることを示しています。- this email which (imho)全体を結びつけます。

8
yshavit

質問3では、synchronized(new Object())は何もせず、何も防止しません。コンパイラーは、他のスレッドがそのオブジェクトで同期できない可能性があることを判別できます(他に何もオブジェクトにアクセスできないため)。これは、Brian Goetzの論文「 Javaの理論と実践:Mustangでの同期の最適化 の明示的な例です。 」.

コンストラクターで同期する必要があったとしても、synchronized(new Object())ブロックが有用だったとしても有用です-つまり、別の長期間有効なオブジェクト。他のメソッドはthisで同期しているため、同じ変数で同期していない場合、可視性の問題が発生します。つまり、コンストラクターでもsynchronized(this)を使用する必要があります。

余談:

thisでの同期は不適切な形式と見なされます。代わりに、いくつかのプライベート最終フィールドで同期します。呼び出し元がオブジェクトで同期し、デッドロックが発生する可能性があります。以下を検討してください。

public class Foo
{
    private int value;
    public synchronized int getValue() { return value; }
    public synchronized void setValue(int value) { this.value = value; }
}

public class Bar
{
    public static void deadlock()
    {
        final Foo foo = new Foo();
        synchronized(foo)
        {
            Thread t = new Thread() { public void run() { foo.setValue(1); } };
            t.start();
            t.join();
        }
    }
}

Fooクラスの呼び出し元にとって、これがデッドロックになるかどうかは明らかではありません。ロックセマンティクスをクラスの内部およびプライベートに保つのが最善です。

2
Edward Thomson