it-swarm.com.de

Warum können Java-Konstruktoren nicht synchronisiert werden?

Gemäß der Java-Sprachspezifikation können Konstruktoren nicht als synchronisiert markiert werden, da andere Threads das erstellte Objekt erst sehen können, wenn der Thread, der es erstellt hat, es beendet hat. Dies scheint ein wenig seltsam zu sein, da ich tatsächlich einen anderen Thread dazu bringen kann, das Objekt zu betrachten, während es erstellt wird:

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

Ich weiß, dass dies ein ziemlich einfallsreiches Beispiel ist, aber theoretisch scheint es so, dass jemand einen realistischeren Fall finden könnte, in dem das Markieren des Konstruktors synchronisiert legitim wäre, um Rennen mit Threads wie diesem zu verhindern.

Meine Frage ist folgende: Gibt es einen Grund dafür, dass Java den synchronisierten Modifikator für einen Konstruktor ausdrücklich verbietet? Vielleicht ist mein obiges Beispiel fehlerhaft oder es gibt wirklich keinen Grund und es ist eine willkürliche Entwurfsentscheidung. In jedem Fall bin ich wirklich neugierig und würde gerne die Antwort wissen.

62
templatetypedef

Wenn Sie wirklich den Rest des Konstruktors mit allen Threads synchronisieren müssen, die auf Ihr noch nicht vollständig erstelltes Objekt verweisen, können Sie einen synchronisierten Block verwenden:

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
       }
    }
}

Normalerweise wird es als schlechter Stil angesehen, Ihr noch nicht erstelltes Objekt so zu "geben", dass ein synchronisierter Konstruktor nicht erforderlich ist.


In einigen Eckfällen wäre ein synchronisierter Konstruktor hilfreich. Hier ist ein realistischeres Beispiel aus der Diskussion der Antwort von 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.");
        }
    }

}

Wir möchten, dass die doSomethingDangerous()-Methode erst aufgerufen wird, wenn die Erstellung unseres SubClass-Objekts abgeschlossen ist, z. Wir wollen nur die Ausgabe "Alles OK". Wenn Sie jedoch nur Ihre SubClass bearbeiten können, haben Sie keine Chance, dies zu erreichen. Wenn der Konstruktor synchronisiert werden könnte, würde er das Problem lösen.

Was wir darüber lernen: Machen Sie niemals etwas so, wie ich es im Superclass-Konstruktor getan habe, wenn Ihre Klasse nicht final ist.

29
Paŭlo Ebermann

Die Frage wurde in einer Diskussionsliste gestellt, die von den Autoren der Java Concurrent API und des Java Memory Model verwendet wird. Mehrere Antworten wurden gegeben, insbesondere Hans Boehm antwortete :

Einige von uns (ich selbst enthalten IIRC) argumentierten tatsächlich während der Java-Speichermodellüberlegungen, dass synchronisierte Konstruktoren erlaubt sein sollten. Jetzt könnte ich in beide Richtungen gehen. Der Client-Code sollte keine Rassen verwenden, um die Referenz zu kommunizieren, daher sollte es keine Rolle spielen. Wenn Sie den Kunden Ihrer Klasse jedoch nicht vertrauen, könnten synchronisierte Konstruktoren möglicherweise nützlich sein. Und das war ein wesentlicher Grund für die Feldsemantik. [...] Wie David gesagt hat, können Sie synchronisierte Blöcke verwenden.

15
assylias

Denn synchronized garantiert, dass Aktionen für dieselben Objekte nicht von mehreren Threads ausgeführt werden. Und wenn der Konstruktor aufgerufen wird, haben Sie noch kein Objekt. Es ist logisch unmöglich, dass zwei Threads auf den Konstruktor desselben Objekts zugreifen.

Auch wenn in Ihrem Beispiel eine Methode vom neuen Thread aufgerufen wird, geht es nicht mehr um den Konstruktor - es geht darum, dass die Zielmethode synchronisiert wird oder nicht.

7
Bozho

In Ihrem Beispiel wird der Konstruktor tatsächlich nur einmal von einem Thread aufgerufen.

Ja, es ist möglich, einen Verweis auf ein unvollständig erstelltes Objekt zu erhalten (einige Diskussionen über das doppelte Prüfen von Sperren und warum es beschädigt ist, zeigen dieses Problem auf), jedoch nicht, indem der Konstruktor ein zweites Mal aufgerufen wird.

Syncronized für den Konstruktor würde verhindern, dass zwei Threads den Konstruktor gleichzeitig für dasselbe Objekt aufrufen. Dies ist nicht möglich, da der Konstruktor niemals zweimal für eine Objektinstanz aufgerufen werden kann.

5
Yishai

Constructor Modifiers Abschnitt in JLS sagt eindeutig

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.

Der Konstruktor muss also nicht synchronisiert werden. 

Es wird auch nicht empfohlen, die Objektreferenz (this) anzugeben, bevor das Objekt erstellt wird. Eine der möglichen mehrdeutigen Situationen wäre die Angabe des Objektreferenz-Superklassen-Konstruktors, wenn ein Unterklassenobjekt erstellt wird.

4
Aniket Thakur

Ich sehe wenig Grund, Konstruktoren die Synchronisation zu verbieten. Es wäre in vielen Szenarien in Multithread-Anwendungen nützlich. Wenn ich das Java Speichermodell richtig verstehe (ich lese http://jeremymanson.blogspot.se/2008/11/what-volatile-meanss-in-Java.html ) Die folgende einfache Klasse könnte von einem synchronisierten Konstruktor profitiert haben.

public class A {
    private int myInt;

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

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

Theoretisch glaube ich, dass ein Aufruf von print() "0" ausgeben könnte . Dies kann passieren, wenn eine Instanz von A von Thread 1 erstellt wird, der Verweis auf die Instanz mit Thread 2 geteilt wird und Thread 2 print() aufruft. Wenn es keine spezielle Synchronisation zwischen dem Schreiben von myInt = 3 von Thread 1 und dem Lesen desselben Felds von Thread 2 gibt, kann für Thread 2 nicht garantiert werden, dass das Schreiben angezeigt wird.

Ein synchronisierter Konstruktor würde dieses Problem beheben. Habe ich recht damit?

3
Frans Lundberg

Beachten Sie, dass Konstruktoren nicht synchronisiert werden können - die Verwendung des synchronizedkeyword mit einem Konstruktor ist ein Syntaxfehler. Das Synchronisieren von Konstruktoren ist nicht sinnvoll, da nur der Thread, der ein Objekt erstellt, während der Erstellung Zugriff darauf haben sollte.

0
sarir

Eine solche Synchronisierung kann in sehr seltenen Fällen sinnvoll sein, aber ich denke, es lohnt sich einfach nicht

  • sie können stattdessen immer einen synchronisierten Block verwenden
  • es würde das Codieren auf ziemlich seltsame Weise unterstützen
  • auf was soll es synchronisieren? Ein Konstruktor ist eine Art statische Methode. Er funktioniert für ein Objekt, wird jedoch ohne aufgerufen. So macht das Synchronisieren in der Klasse auch (etwas) Sinn!

Wenn Sie Zweifel haben, lassen Sie es aus.

0
maaartinus

Der folgende Code kann das erwartete Ergebnis für den synchronisierten Konstruktor erzielen.

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