Was ist der Grund, warum "synchronisiert" in Java 8-Schnittstellenmethoden nicht zulässig ist?

210

In Java 8 kann ich leicht schreiben:

interface Interface1 {
    default void method1() {
        synchronized (this) {
            // Something
        }
    }

    static void method2() {
        synchronized (Interface1.class) {
            // Something
        }
    }
}

Ich werde die vollständige Synchronisationssemantik erhalten, die ich auch in Klassen verwenden kann. Ich kann den synchronizedModifikator jedoch nicht für Methodendeklarationen verwenden:

interface Interface2 {
    default synchronized void method1() {
        //  ^^^^^^^^^^^^ Modifier 'synchronized' not allowed here
    }

    static synchronized void method2() {
        // ^^^^^^^^^^^^ Modifier 'synchronized' not allowed here
    }
}

Nun kann man argumentieren , dass die beiden Schnittstellen verhalten sich die gleiche Weise , außer dass Interface2ein legt Vertrag auf method1()und auf method2(), die ein wenig stärker als das , was der Interface1Fall ist. Natürlich könnten wir auch argumentieren, dass defaultImplementierungen keine Annahmen über den konkreten Implementierungsstatus treffen sollten oder dass ein solches Schlüsselwort einfach nicht an Bedeutung gewinnen würde.

Frage:

Was ist der Grund, warum die JSR-335-Expertengruppe beschlossen hat, synchronizedSchnittstellenmethoden nicht zu unterstützen ?

Lukas Eder
quelle
1
Synchronisiert ist ein Implementierungsverhalten und ändert das endgültige Bytecode-Ergebnis des Compilers, sodass es neben einem Code verwendet werden kann. Es hat keinen Sinn in der Methodendeklaration. Es sollte verwirrend sein, was der Compiler produziert, wenn sich die Synchronisierung auf der Abstraktionsschicht befindet.
Martin Strejc
@MartinStrejc: Das könnte eine Erklärung für das Weglassen sein default synchronized, aber nicht unbedingt für static synchronized, obwohl ich akzeptieren würde, dass letzteres aus Konsistenzgründen weggelassen worden sein könnte.
Lukas Eder
1
Ich bin mir nicht sicher, ob diese Frage einen Wert hinzufügt, da der synchronizedModifikator in Unterklassen möglicherweise überschrieben wird. Daher wäre es nur wichtig, wenn es etwas als endgültige Standardmethode gäbe. (Ihre andere Frage)
Skiwi
@skiwi: Das übergeordnete Argument reicht nicht aus. Unterklassen können Methoden überschreiben, die synchronizedin Superklassen deklariert sind, wodurch die Synchronisation effektiv entfernt wird. Es würde mich nicht wundern , wenn nicht unterstützt synchronizedund nicht unterstützt finalwird, möglicherweise aufgrund von Mehrfachvererbung (z. B. Vererbung void x() und synchronized void x() usw.). Aber das ist Spekulation. Ich bin neugierig auf einen maßgeblichen Grund, falls es einen gibt.
Lukas Eder
2
>> "Unterklassen können Methoden überschreiben, die in Superklassen als synchronisiert deklariert sind, wodurch die Synchronisation effektiv entfernt wird" ... nur wenn sie nicht aufgerufen werden, superwas eine vollständige Neuimplementierung und einen möglichen Zugriff auf private Mitglieder erfordert. Übrigens gibt es einen Grund, warum diese Methoden als "Verteidiger" bezeichnet werden - sie sind vorhanden, um das Hinzufügen neuer Methoden zu erleichtern.
Bests

Antworten:

260

Während es auf den ersten Blick offensichtlich erscheint, dass man den synchronizedModifikator bei Standardmethoden unterstützen möchte , stellt sich heraus, dass dies gefährlich wäre und daher verboten war.

Synchronisierte Methoden sind eine Abkürzung für eine Methode, die sich so verhält, als ob der gesamte Körper in einem synchronizedBlock eingeschlossen ist, dessen Sperrobjekt der Empfänger ist. Es mag sinnvoll erscheinen, diese Semantik auch auf Standardmethoden auszudehnen. Schließlich handelt es sich auch um Instanzmethoden mit einem Empfänger. (Beachten Sie, dass synchronizedMethoden vollständig eine syntaktische Optimierung sind; sie werden nicht benötigt, sie sind nur kompakter als der entsprechende synchronizedBlock. Es gibt ein vernünftiges Argument dafür, dass dies in erster Linie eine vorzeitige syntaktische Optimierung war und dass Methoden synchronisiert wurden verursachen mehr Probleme als sie lösen, aber dieses Schiff ist vor langer Zeit gesegelt.)

Warum sind sie gefährlich? Bei der Synchronisierung geht es um das Sperren. Beim Sperren geht es darum, den gemeinsamen Zugriff auf den veränderlichen Status zu koordinieren. Jedes Objekt sollte über eine Synchronisationsrichtlinie verfügen, die bestimmt, welche Sperren welche Statusvariablen schützen. (Siehe Java-Parallelität in der Praxis , Abschnitt 2.4.)

Viele Objekte verwenden als Synchronisationsrichtlinie das Java Monitor Pattern (JCiP 4.1), bei dem der Status eines Objekts durch seine interne Sperre geschützt wird. Dieses Muster hat nichts Magisches oder Besonderes, aber es ist praktisch, und die Verwendung des synchronizedSchlüsselworts für Methoden setzt implizit dieses Muster voraus.

Es ist die Klasse, die den Status besitzt, der die Synchronisierungsrichtlinie dieses Objekts bestimmen kann. Schnittstellen besitzen jedoch nicht den Status der Objekte, in die sie eingemischt sind. Die Verwendung einer synchronisierten Methode in einer Schnittstelle setzt daher eine bestimmte Synchronisierungsrichtlinie voraus, für die Sie jedoch keine vernünftige Grundlage haben. Dies kann durchaus der Fall sein Die Verwendung der Synchronisierung bietet keinerlei zusätzliche Thread-Sicherheit (möglicherweise synchronisieren Sie mit der falschen Sperre). Dies würde Ihnen das falsche Gefühl des Vertrauens geben, dass Sie etwas gegen die Thread-Sicherheit unternommen haben, und keine Fehlermeldung sagt Ihnen, dass Sie die falsche Synchronisierungsrichtlinie annehmen.

Es ist bereits schwierig genug, eine Synchronisierungsrichtlinie für eine einzelne Quelldatei konsistent zu verwalten. Es ist noch schwieriger sicherzustellen, dass eine Unterklasse die von ihrer Oberklasse definierte Synchronisationsrichtlinie korrekt einhält. Der Versuch, dies zwischen solchen lose gekoppelten Klassen (einer Schnittstelle und den möglicherweise vielen Klassen, die sie implementieren) zu tun, wäre nahezu unmöglich und sehr fehleranfällig.

Wofür wäre das Argument angesichts all dieser Argumente? Es scheint, dass es hauptsächlich darum geht, dass sich Schnittstellen eher wie Merkmale verhalten. Während dies ein verständlicher Wunsch ist, ist das Design Center für Standardmethoden die Schnittstellenentwicklung, nicht "Traits--". Wo die beiden konsequent erreicht werden konnten, haben wir uns bemüht, aber wo einer im Konflikt mit dem anderen steht, mussten wir uns für das primäre Designziel entscheiden.

Brian Goetz
quelle
26
Beachten Sie auch, dass in JDK 1.1 der synchronizedMethodenmodifikator in der Javadoc-Ausgabe angezeigt wurde, was die Leute irreführte, zu glauben, dass er Teil der Spezifikation war. Dies wurde in JDK 1.2 behoben. Selbst wenn es auf einer öffentlichen Methode angezeigt wird, ist der synchronizedModifikator Teil der Implementierung und nicht des Vertrags. (Ähnliche Überlegungen und Behandlungen fanden für den nativeModifikator statt.)
Stuart Marks
15
Ein häufiger Fehler in frühen Java-Programmen war, genug zu streuen synchronizedund sichere Komponenten einzufädeln, und Sie hatten ein fast thread-sicheres Programm. Das Problem war, dass dies normalerweise in Ordnung funktionierte, aber auf überraschende und spröde Weise kaputt ging. Ich bin damit einverstanden, dass das Verständnis der Funktionsweise Ihrer Sperre ein Schlüssel für robuste Anwendungen ist.
Peter Lawrey
10
@BrianGoetz Sehr guter Grund. Aber warum ist synchronized(this) {...}eine defaultMethode erlaubt ? (Wie in Lukas 'Frage gezeigt.) Erlaubt das nicht, dass die Standardmethode auch den Status der Implementierungsklasse besitzt? Wollen wir das nicht auch verhindern? Benötigen wir eine FindBugs-Regel, um die Fälle zu finden, für die nicht informierte Entwickler dies tun?
Geoffrey De Smet
17
@Geoffrey: Nein, es gibt keinen Grund, dies einzuschränken (obwohl es immer mit Vorsicht verwendet werden sollte). Für den Synchronisierungsblock muss der Autor ein Sperrobjekt explizit auswählen. Auf diese Weise können sie an der Synchronisierungsrichtlinie eines anderen Objekts teilnehmen, wenn sie wissen, um welche Richtlinie es sich handelt. Der gefährliche Teil ist die Annahme, dass die Synchronisierung auf "this" (was Synchronisierungsmethoden tun) tatsächlich sinnvoll ist. Dies muss eine explizitere Entscheidung sein. Trotzdem erwarte ich, dass Synchronisationsblöcke in Schnittstellenmethoden ziemlich selten sind.
Brian Goetz
6
@GeoffreyDeSmet: Aus dem gleichen Grund können Sie zB tun synchronized(vector). Wenn Sie sicher sein möchten, sollten Sie niemals ein öffentliches Objekt (wie sich thisselbst) zum Sperren verwenden.
Yogu
0
public class ParentSync {

public synchronized void parentStart() {
    System.out.println("I am " + this.getClass() + " . parentStarting. now:" + nowStr());
    try {
        Thread.sleep(30000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("I am " + this.getClass() + " . parentFinished. now" + nowStr());
}

private String nowStr() {
    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
}
}


public class SonSync1 extends ParentSync {
public void sonStart() {
    System.out.println("I am " + this.getClass() + ". sonStarting,calling parent now ... ");
    super.parentStart();
    System.out.println("I am " + this.getClass() + ". sonFinished");
}
}



public class SonSync2 extends ParentSync {

public void sonStart() {
    System.out.println("I am " + this.getClass() + ". sonStarting,calling parent now ... ");
    super.parentStart();
    System.out.println("I am " + this.getClass() + ". sonFinished");
}
}



public class SyncTest {
public static void main(String[] args) throws Exception {

    new Thread(() -> {
        new SonSync1().sonStart();
    }).start();

    new Thread(() -> {
        new SonSync2().sonStart();
    }).start();

    System.in.read();
}
}

Ergebnis:

I am class com.common.interface18_design.whynotsync_onmethod.SonSync1. sonStarting,calling parent now ... 
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2. sonStarting,calling parent now ... 
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2 . parentStarting. now:2019-04-18 09:50:08
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1 . parentStarting. now:2019-04-18 09:50:08
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1 . parentFinished. now2019-04-18 09:50:38
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1. sonFinished
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2 . parentFinished. now2019-04-18 09:50:38
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2. sonFinished

(Entschuldigung für die Verwendung der Elternklasse als Beispiel)

Aus dem Ergebnis können wir erkennen, dass die übergeordnete Klassensperre jeder Unterklasse gehört. SonSync1- und SonSync2-Objekte haben unterschiedliche Objektsperren. Jedes Schloss ist unabhängig. In diesem Fall denke ich, dass es nicht gefährlich ist, eine in einer übergeordneten Klasse synchronisierte oder eine gemeinsame Schnittstelle zu verwenden. könnte jemand mehr dazu erklären?

zhenke zhu
quelle