web-dev-qa-db-ja.com

なぜJavaコンストラクターを同期できないのですか?

Java Language Specification によると、作成中のスレッドが終了するまで他のスレッドは作成中のオブジェクトを見ることができないため、コンストラクターを同期済みとしてマークできません。奇妙なのは、構築中に別のスレッドにオブジェクトを表示させることができるためです。

public class Test {
    public Test() {
       final Test me = this;
       new Thread() {
           @Override
           public void run() {
               // ... Reference 'me,' the object being constructed
           }
       }.start();
    }
}

私はこれがかなり不自然な例であることを知っていますが、理論的には、誰かがこのようなスレッドとの競合を防ぐために、コンストラクタを同期化することを合法化するより現実的なケースを考え出すことができるようです。

私の質問はこれです:Javaがコンストラクターで同期修飾子を具体的に許可しない理由はありますか?おそらく上記の例に欠陥があるか、おそらく理由がなく、それは任意の設計決定ですどちらの場合でも、私は本当に興味があり、答えを知りたいです。

63
templatetypedef

コンストラクタの残りの部分と、まだ完全には構築されていないオブジェクトへの参照を取得するスレッドとの同期が本当に必要な場合は、synchronized-blockを使用できます。

_public class Test {
    public Test() {
       final Test me = this;
       synchronized(this) {
          new Thread() {
             @Override
             public void run() {
                // ... Reference 'me,' the object being constructed
                synchronized(me) {
                   // do something dangerous with 'me'.
                }
             }
          }.start();
          // do something dangerous with this
       }
    }
}
_

通常、このようにまだ構築されていないオブジェクトを「渡す」ことは悪いスタイルと見なされるため、同期化されたコンストラクタは必要ありません。


場合によっては、同期コンストラクターが便利です。 Bozhoの答えの議論からのより現実的な例はここにあります:

_public abstract class SuperClass {

   public SuperClass() {
       new Thread("evil") { public void run() {
          doSomethingDangerous();
       }}).start();
       try {
          Thread.sleep(5000);
       }
       catch(InterruptedException ex) { /* ignore */ }
   }

   public abstract void doSomethingDangerous();

}

public class SubClass extends SuperClass {
    int number;
    public SubClass () {
        super();
        number = 2;
    }

    public synchronized void doSomethingDangerous() {
        if(number == 2) {
            System.out.println("everything OK");
        }
        else {
            System.out.println("we have a problem.");
        }
    }

}
_

doSomethingDangerous()メソッドは、SubClassオブジェクトの構築が完了した後にのみ呼び出されるようにします。 「すべてOK」の出力のみが必要です。ただし、この場合、サブクラスのみを編集できる場合、これを達成する機会はありません。コンストラクターを同期できれば、問題は解決します。

したがって、私たちがこれについて学ぶことは、あなたのクラスがファイナルではない場合、スーパークラスコンストラクタで私がここでやったようなことは決してしないでください-そしてあなたのコンストラクタからあなた自身のクラスの非ファイナルメソッドを呼び出さないでください。

30
Paŭlo Ebermann

質問は、Java同時APIとJavaメモリモデル。特にいくつかの答えが与えられました。 ハンス・ベームが答えた

私たちの中には(IIRCを含めて)実際にJavaメモリモデルの審議中に、同期コンストラクターを許可する必要があると主張しました。しかし、[クラス]のクライアントを信頼していない場合は、同期コンストラクタが役立つ可能性があると思います。それが、最終フィールドセマンティクスの背後にある多くの理由でした。 ]デビッドが言ったように、同期ブロックを使用できます。

17
assylias

synchronizedは、同じオブジェクトに対するアクションが複数のスレッドによって実行されないことを保証するためです。そして、コンストラクターが呼び出されたとき、あなたはまだオブジェクトを持っていません。 2つのスレッドが同じオブジェクトのコンストラクターにアクセスすることは論理的に不可能です。

この例では、メソッドが新しいスレッドによって呼び出された場合でも、コンストラクターではなく、ターゲットメソッドが同期されているかどうかについてです。

8
Bozho

Constructor Modifiersセクション JLSで明確に言う

There is no practical need for a constructor to be synchronized, because it would
lock the object under construction, which is normally not made available to other
threads until all constructors for the object have completed their work.

したがって、コンストラクターを同期する必要はありません。

また、オブジェクトを作成する前にオブジェクトreference(this)を提供することはお勧めしません。あいまいな状況の1つは、サブクラスオブジェクトの作成中にオブジェクト参照をスーパークラスコンストラクターに渡すことです。

5
Aniket Thakur

この例では、コンストラクターは実際には1つのスレッドから1回だけ呼び出されます。

はい、不完全に構築されたオブジェクトへの参照を取得することは可能ですが(ダブルチェックロックに関するいくつかの議論と、なぜ壊れているかがこの問題を明らかにします)、しかしコンストラクターを2度呼び出すことによってではありません。

コンストラクターで同期すると、2つのスレッドが同じオブジェクトのコンストラクターを同時に呼び出すことができなくなります。これは、オブジェクトインスタンスでコンストラクターをピリオドで2回呼び出すことができないため不可能です。

5
Yishai

コンストラクターの同期を禁止する理由はほとんどありません。マルチスレッドアプリケーションの多くのシナリオで役立ちます。 Java Memory Modelを正しく理解している場合( http://jeremymanson.blogspot.se/2008/11/what-volatile-means-in-Java.html)を読みます )次の単純なクラスは、同期化されたコンストラクタの恩恵を受けることができます。

_public class A {
    private int myInt;

    public /*synchronized*/ A() {
        myInt = 3;
    }

    public synchronized void print() {
        System.out.println(myInt);
    }
}
_

理論的には、print()couldの呼び出しは "0"を出力すると信じています。これは、Aのインスタンスがスレッド1によって作成され、インスタンスへの参照がスレッド2と共有され、スレッド2がprint()を呼び出す場合に発生する可能性があります。スレッド1の_myInt = 3_の書き込みとスレッド2による同じフィールドの読み取りとの間に特別な同期がない場合、スレッド2は書き込みを確認することは保証されません。

同期コンストラクターはこの問題を修正します。私はこれについて正しいですか?

3
Frans Lundberg

コンストラクタは同期できないことに注意してください。コンストラクタでsynchronizedキーワードを使用すると、構文エラーになります。コンストラクタの同期は意味がありません。なぜなら、オブジェクトを作成しているスレッドだけが、構築中にアクセスできるはずだからです。

0
sarir

このような同期は、非常にまれなケースでは理にかなっている場合がありますが、それだけの価値はないと思います。

  • 代わりに常に同期ブロックを使用できます
  • かなり奇妙な方法でコーディングをサポートします
  • 何を同期すべきかコンストラクターは一種の静的メソッドであり、オブジェクトに対して機能しますが、オブジェクトなしで呼び出されます。そのため、クラスでの同期も(一部の)意味をなします!

疑わしい場合は、除外します。

0
maaartinus

次のコードは、同期コンストラクターに期待される結果を達成できます。

public class SynchronisedConstructor implements Runnable {

    private int myInt;

    /*synchronized*/ static {
        System.out.println("Within static block");
    }

    public SynchronisedConstructor(){
        super();
        synchronized(this){
            System.out.println("Within sync block in constructor");
            myInt = 3;
        }
    }

    @Override
    public void run() {
        print();
    }

    public synchronized void print() {
        System.out.println(Thread.currentThread().getName());
        System.out.println(myInt);
    }

    public static void main(String[] args) {

        SynchronisedConstructor sc = new SynchronisedConstructor();

        Thread t1 = new Thread(sc);
        t1.setName("t1");
        Thread t2 = new Thread(sc);
        t2.setName("t2");

        t1.start();
        t2.start();
    }
}
0
Nimesh