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

Lesezeit: 8 Minuten

Benutzer-Avatar
Lukas Edder

In Java 8 kann ich einfach 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 im Unterricht verwenden kann. Allerdings kann ich die nicht verwenden synchronized Modifikator für Methodendeklarationen:

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 sich die beiden Schnittstellen abgesehen davon gleich verhalten Interface2 stellt ein Vertrag an method1() und weiter method2()das ist ein bisschen stärker als was Interface1 tut. Das könnten wir natürlich auch argumentieren default Implementierungen sollten keine Annahmen über den konkreten Implementierungsstatus treffen oder dass ein solches Schlüsselwort einfach nicht ins Gewicht fallen würde.

Frage:

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

  • Synchronized ist ein Implementierungsverhalten und ändert das endgültige Byte-Code-Ergebnis, das vom Compiler erstellt wurde, 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 Synchronisation auf der Abstraktionsschicht befindet.

    – Martin Strejc

    4. Mai 2014 um 7:18 Uhr

  • @MartinStrejc: Das könnte eine Erklärung für das Weglassen sein default synchronizedaber nicht unbedingt für static synchronizedobwohl ich akzeptieren würde, dass letzteres aus Konsistenzgründen weggelassen wurde.

    – Lukas Eder

    4. Mai 2014 um 7:31 Uhr

  • Ich bin mir nicht sicher, ob diese Frage einen Mehrwert bringt synchronized Der Modifikator kann in Unterklassen überschrieben werden, daher wäre es nur wichtig, wenn es etwas als endgültige Standardmethode gibt. (Deine andere Frage)

    – skiwi

    4. Mai 2014 um 12:40 Uhr

  • @skiwi: Das übergeordnete Argument reicht nicht aus. Unterklassen können deklarierte Methoden überschreiben synchronized in Superklassen, wodurch die Synchronisation effektiv entfernt wird. Es würde mich nicht wundern, wenn ich nicht unterstützt werde synchronized und nicht unterstützt final ist aber verwandt, vielleicht wegen Mehrfachvererbung (zB erben void x() und synchronized void x(), etc.). Aber das ist Spekulation. Ich bin neugierig auf einen maßgeblichen Grund, falls es einen gibt.

    – Lukas Eder

    4. Mai 2014 um 13:32 Uhr


  • >> “Unterklassen können Methoden außer Kraft setzen, die in Oberklassen als synchronisiert deklariert sind, wodurch die Synchronisation effektiv entfernt wird” … nur wenn sie nicht aufrufen super was eine vollständige Neuimplementierung und einen möglichen Zugriff auf private Mitglieder erfordert. Übrigens gibt es einen Grund, warum diese Methoden “Verteidiger” genannt werden – sie sind vorhanden, um das Hinzufügen neuer Methoden zu erleichtern.

    – bestes

    10. Mai 2014 um 6:10 Uhr

Benutzer-Avatar
Brian Götz

Während es zunächst offensichtlich erscheinen mag, dass man die unterstützen möchte synchronized Modifikator auf Standardmethoden, stellte 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 wäre der gesamte Körper in a eingeschlossen synchronized Block, dessen Sperrobjekt der Empfänger ist. Es mag sinnvoll erscheinen, diese Semantik auch auf Standardmethoden auszudehnen; schließlich sind sie auch Instanzmethoden mit einem Empfänger. (Beachten Sie, dass synchronized Methoden sind ausschließlich eine syntaktische Optimierung; Sie werden nicht benötigt, sie sind nur kompakter als die entsprechenden synchronized Block. Es gibt ein vernünftiges Argument dafür, dass dies in erster Linie eine verfrühte syntaktische Optimierung war und dass synchronisierte Methoden mehr Probleme verursachen als sie lösen, aber dieses Schiff ist vor langer Zeit abgefahren.)

Warum sind sie also gefährlich? Bei der Synchronisierung geht es um das Sperren. Beim Sperren geht es darum, den gemeinsamen Zugriff auf den veränderlichen Zustand zu koordinieren. Jedes Objekt sollte über eine Synchronisierungsrichtlinie verfügen, die festlegt, welche Sperren welche Zustandsvariablen schützen. (Sehen Java-Parallelität in der PraxisAbschnitt 2.4.)

Viele Objekte verwenden als Synchronisierungsrichtlinie die Java-Monitormuster (JCiP 4.1), bei dem der Zustand eines Objekts durch seine intrinsische Sperre geschützt wird. Dieses Muster hat nichts Magisches oder Besonderes, aber es ist praktisch und die Verwendung von synchronized Schlüsselwort für Methoden geht implizit von diesem Muster aus.

Es ist die Klasse, die den Zustand besitzt, der die Synchronisierungsrichtlinie dieses Objekts bestimmen darf. Schnittstellen besitzen jedoch nicht den Zustand der Objekte, in die sie eingemischt werden. Die Verwendung einer synchronisierten Methode in einer Schnittstelle setzt also eine bestimmte Synchronisationsrichtlinie voraus, für deren Annahme Sie jedoch keine vernünftige Grundlage haben, sodass dies durchaus der Fall sein kann Die Verwendung der Synchronisation bietet keinerlei zusätzliche Thread-Sicherheit (möglicherweise synchronisieren Sie auf der falschen Sperre). Dies würde Ihnen das falsche Gefühl der Zuversicht geben, dass Sie etwas für die Thread-Sicherheit getan haben, und keine Fehlermeldung sagt Ihnen, dass Sie von der falschen Synchronisierungsrichtlinie ausgehen.

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

Angesichts all dieser Gegenargumente, was wäre das Argument dafür? Es scheint, dass es ihnen hauptsächlich darum geht, Schnittstellen dazu zu bringen, sich mehr wie Traits zu verhalten. Obwohl dies ein verständlicher Wunsch ist, ist das Designzentrum für Standardmethoden die Interface-Evolution, nicht “Traits–“. Wo beides konsequent erreicht werden konnte, haben wir uns darum bemüht, aber wo das eine im Widerspruch zum anderen steht, mussten wir uns für das primäre Designziel entscheiden.

  • Beachten Sie auch, dass in JDK 1.1 die synchronized Methodenmodifikator tauchte in der Javadoc-Ausgabe auf und verleitete die Leute zu der Annahme, dass er Teil der Spezifikation sei. Dies wurde in JDK 1.2 behoben. Auch wenn es auf einer öffentlichen Methode erscheint, die synchronized modifier ist Teil der Implementierung, nicht des Vertrags. (Ähnliche Argumentation und Behandlung gab es für die native Modifikator.)

    – Stuart Marks

    5. Mai 2014 um 4:33 Uhr

  • Ein häufiger Fehler in frühen Java-Programmen bestand darin, genug zu streuen synchronized und Thread-sichere Komponenten herum und Sie hatten ein fast Thread-sicheres Programm. Das Problem war, dass dies normalerweise gut funktionierte, aber auf überraschende und spröde Weise kaputt ging. Ich stimme zu, dass das Verständnis, wie Ihre Verriegelung funktioniert, ein Schlüssel zu robusten Anwendungen ist.

    – Peter Lawrey

    5. Mai 2014 um 10:27 Uhr

  • @BrianGoetz Sehr guter Grund. Aber warum ist synchronized(this) {...} erlaubt in a default Methode? (Wie in Lukas Frage gezeigt.) Erlaubt das der Standardmethode nicht auch, den Zustand der Implementierungsklasse zu besitzen? Wollen wir das nicht auch verhindern? Brauchen wir eine FindBugs-Regel, um die Fälle zu finden, bei denen uninformierte Entwickler das tun?

    – Geoffrey de Smet

    5. Mai 2014 um 10:28 Uhr


  • @Geoffrey: Nein, es gibt keinen Grund, dies einzuschränken (obwohl es immer mit Vorsicht verwendet werden sollte). Der Synchronisierungsblock erfordert, dass der Autor explizit ein Sperrobjekt auswählt; Dadurch können sie an der Synchronisierungsrichtlinie eines anderen Objekts teilnehmen, wenn sie wissen, was diese Richtlinie ist. 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. Allerdings erwarte ich, dass Sync-Blöcke in Schnittstellenmethoden ziemlich selten sind.

    – Brian Götz

    5. Mai 2014 um 14:08 Uhr

  • @GeoffreyDeSmet: Aus dem gleichen Grund können Sie z synchronized(vector). Wenn Sie auf Nummer sicher gehen möchten, sollten Sie niemals ein öffentliches Objekt (wie z this selbst) zum Verriegeln.

    – Jogu

    5. Mai 2014 um 21:26 Uhr

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 übergeordneten Klasse 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ängigkeit. In diesem Fall denke ich also, dass es nicht gefährlich ist, eine synchronisierte in einer übergeordneten Klasse oder einer gemeinsamen Schnittstelle zu verwenden. könnte jemand mehr dazu erklären?

1355350cookie-checkWas ist der Grund, warum „synchronisiert“ in Java 8-Schnittstellenmethoden nicht zulässig ist?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy