2011-02-02 30 views
55

the Java Language Specificationによれば、作成中のオブジェクトが他のスレッドで作成されているのを見ることができないので、コンストラクタを同期化することはできません。私は確かに構築されている間、別のスレッドがオブジェクトを見ることができますので、これは、少し奇妙に思える:Javaコンストラクタを同期できないのはなぜですか?

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

が、私はこれはかなり不自然な例であることを知っているが、誰かが思い付くことができることを理論的に思えますこのようなスレッドを持つレースを防ぐために、コンストラクタを同期してマークするのが正当なより現実的なケースです。

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

+9

他にも、コンストラクタが完了する前に "this"リファレンスがエスケープされないようにすることを強くお勧めします。 –

+1

@Mike Q-私はこれまでにこれを聞いたことがありますが、理由を完全に理解していません。なぜ特別な理由がありますか?オブジェクトの初期化を完了する前にこれを参照した場合、オブジェクトが発生していることがわかりましたが、コンストラクタで最後に行った場合はどうなりますか? – templatetypedef

+2

これは本当に別の質問の対象ですが、コンストラクタで最後に行うことであっても、オブジェクトがサブクラス化されている場合、サブクラスは構築が完了していません。このクラスがfinalで、コンストラクタをチェーンしていない場合(this(...)を呼び出す)、チェーンコールの後に何か他のことをしてください。もちろん、これらの決定のいずれかが変更される可能性があります(後で2番目のコンストラクタを追加することもできます)。 – Yishai

答えて

25

あなたは本当にあなたが同期ブロックを使用することができ、とにかくあなたの未完全な構成のオブジェクトへの参照を取得する任意のスレッド対コンストラクタの残りの同期が必要な場合:通常

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()メソッドだけ呼び出されることを望みます「すべてOK」という出力のみが必要です。しかし、この場合、あなたのSubClassだけを編集することができれば、これを達成するチャンスはありません。コンストラクタを同期させることができれば、問題を解決できます。

私たちはこれについて学んでいます。あなたのクラスがfinalでない場合、スーパークラスのコンストラクタでここで行ったようなことは絶対にしないでください。あなたのコンストラクタから独自のクラスのfinalメソッドを呼び出さないでください。

+0

ああ、私にそれを打つ:) +1 –

+1

@Paulo、あなたの例は非常に考案されています。まず、コンストラクタから抽象メソッドを呼び出すのは悪い習慣ですが、重要なことは、スレッドがない場合、サブクラスは*正確に同じ問題を持ちます。だから同期されて何が追加されるのだろうか?サブクラスを引き起こしている間に非常に起こりそうなケースをサポートすることは、どちらのケースでもうまくいく可能性のある、より一般的なケースを回避する必要があります。 – Yishai

+0

@Yishai:そうです、その例はあまり現実的ではありません。しかし、コンストラクタ内で独自のオブジェクトを他のスレッドに公開するクラスを見たことがあります(必ずしも同じクラスでなく、必ずしもスリープ状態ではありません)。そして、同期コンストラクタが助けになるような問題を抱えることがあります。もちろん、私の例では、このメソッドはスーパークラスのコンストラクタから呼び出されるために自分自身を保護する必要があります。 –

7

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

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

+0

2つのスレッドがどちらもコンストラクタを呼び出すことはできませんが、一方のスレッドがコンストラクタを呼び出して、クラスの異なる同期メソッドを呼び出そうとしている別のスレッドを起動するとどうなりますか?この場合、コンストラクタを同期させると、コンストラクタが終了するまで、2番目のスレッドが同期メソッドを呼び出さなくなります。 – templatetypedef

+1

それはもうコンストラクタではなく、問題のメソッドについてです。 – Bozho

+3

あなたはコンストラクタ内で同期化された(this){}を実行できます。コンストラクタ自体に同期させるのはナンセンスです。どのようにしてオブジェクトを生成するプロセスを開始する前に、そのオブジェクトにmutexを取得していますか? – Affe

5

例では、コンストラクタは実際には1つのスレッドから1回しか呼び出されません。

はい、コンストラクタをもう一度呼び出すのではなく、不完全に構築されたオブジェクトへの参照を取得することができます(二重チェックロックに関するいくつかの議論と、それがなぜこの問題を明らかにするか)。

コンストラクタを同期化すると、2つのスレッドが同じオブジェクトのコンストラクタを同時に呼び出すことができなくなります。これは不可能です。オブジェクトインスタンスでコンストラクタを2回呼び出すことは決してできません。

+0

Downvoterで表現することはできません、何らかの理由がありますか? – Yishai

+4

私はdownvoteしませんでしたが、考えられる理由:コンストラクタで同期されると、同じスレッドのコンストラクタを呼び出す2つのスレッド(これは不可能です)だけでなく、このオブジェクトのsynchronizedメソッドを呼び出すことも避けられますこのオブジェクトをパラメータとしてブロックします)。 –

1

コンストラクタの同期を禁止する理由はほとんどありません。これは、マルチスレッドアプリケーションの多くのシナリオで役立ちます。私がJavaメモリモデルを正しく理解していれば(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()でし印刷 "0" への呼び出しを信じています。これは、Aのインスタンスがスレッド1によって作成され、インスタンスへの参照がスレッド2と共有され、スレッド2がprint()を呼び出す場合に発生します。スレッド1の書き込みmyInt = 3とスレッド2の同じフィールドの読み取りとの間に特別な同期がない場合、スレッド2は書き込みを見ることが保証されません。

同期コンストラクタによってこの問題が解決されます。私はこれについて正しいですか?

+0

(i)あなたが記述する問題は、Aのインスタンスが適切に公開されていないということです。その場合、その観測された状態は実際に何かになる可能性があります。(ii) 'synchronized(this){myInt = 3; } 'を使用して問題を解決するか、finalとして' myInt'をマークします。 – assylias

+0

'myInt'変数に' volatile'キーワードを付けることもできます。 – ponkin

+1

必ずしもそうではありません。オブジェクトの参照が完全に構築される前に他のスレッドにリークされた場合、他のスレッドもこのオブジェクトの非同期メソッドを呼び出すことができます。その場合、コンストラクタの同期は決して役に立ちません。後者の場合を回避することは、漏れを実行する必要がないことを意味し、その後、コンストラクタを同期させることは不要になる。 – lcn

0

次のコードは、同期コンストラクタで期待される結果を得ることができます。

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(); 
    } 
} 
14

質問には、Javaの同時APIとJavaメモリモデルの作家が使用するディスカッションリストに引き上げられました。いくつかの回答はHans Boehm replied特に、与えられた:

私たちの一部は、(自分がIIRC含む)、実際に同期したコンストラクタが許可されなければならないのJavaメモリモデルの審議中に主張しました。今私はそれのどちらかに行くことができます。クライアントコードは参照を伝えるためにレースを使用すべきではないので、重要ではありません。しかし、あなたが[あなたのクラス]のクライアントを信頼しなければ、同期コンストラクタが役に立つかもしれないと私は思います。そして、それは最終的な分野の意味論の背後にある多くの理由でした。 [...] Davidが言ったように、あなたは同期ブロックを使うことができます。 JLSで

3

Constructor Modifiers section明確

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. 

だからコンストラクタが同期されるための必要がないと言います。

また、オブジェクトが作成される前にオブジェクト参照(this)を渡すことは推奨されません。可能なあいまいな状況の1つは、サブクラス・オブジェクトが作成されているときにスーパークラス・コンストラクタであるオブジェクト参照を与えることです。

+0

他のスレッドで同期化することができます。 – momomo

0

このような同期は、いくつかの非常にまれなケースでは意味をなさないかもしれませんが、私は推測する、それだけでそれだけの価値はありません。

  • あなたは常にそれがでコーディングをサポートしたい
  • 代わりにsynchronizedブロックを使用することができますかなり奇妙な方法
  • それは何を同期する必要がありますか?コンストラクタは一種の静的メソッドであり、オブジェクト上で動作しますが、オブジェクトなしで呼び出されます。クラスで同期をとると、(いくらかの)意味があります!

疑わしい場合は除外してください。

関連する問題