Quelle est la raison pour laquelle «synchronisé» n'est pas autorisé dans les méthodes d'interface Java 8?

210

En Java 8, je peux facilement écrire:

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

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

J'obtiendrai la sémantique de synchronisation complète que je pourrai également utiliser en classe. Je ne peux cependant pas utiliser le synchronizedmodificateur sur les déclarations de méthode:

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

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

Maintenant, on peut dire que les deux interfaces se comportent de la même manière , sauf que Interface2établit un contrat sur method1()et method2(), ce qui est un peu plus fort que ce qui le Interface1fait. Bien sûr, nous pourrions également affirmer que les defaultimplémentations ne devraient pas faire d'hypothèses sur l'état d'implémentation concret, ou qu'un tel mot clé ne pourrait tout simplement pas peser.

Question:

Quelle est la raison pour laquelle le groupe d'experts JSR-335 a décidé de ne pas prendre en charge synchronizedles méthodes d'interface?

Lukas Eder
la source
1
Synchronisé est un comportement d'implémentation et il modifie le résultat final du code d'octet effectué par le compilateur afin qu'il puisse être utilisé à côté d'un code. Cela n'a aucun sens dans la déclaration de méthode. Cela devrait prêter à confusion ce que le compilateur produit s'il est synchronisé sur la couche d'abstraction.
Martin Strejc
@MartinStrejc: Cela pourrait être une explication pour l'omission default synchronized, mais pas nécessairement pour static synchronized, bien que j'accepterais que ce dernier ait pu être omis pour des raisons de cohérence.
Lukas Eder
1
Je ne sais pas si cette question ajoute une valeur car le synchronizedmodificateur peut être remplacé dans les sous-classes, donc cela n'aurait d'importance que s'il y avait quelque chose comme méthodes par défaut finales. (Votre autre question)
skiwi
@skiwi: L'argument prioritaire n'est pas suffisant. Les sous-classes peuvent remplacer les méthodes déclarées synchronizeddans les super classes, supprimant ainsi la synchronisation. Je ne serais pas surpris que le fait de ne pas soutenir synchronizedet de ne pas soutenir finalsoit lié, cependant, peut-être à cause de l'héritage multiple (par exemple, héritage void x() et synchronized void x() , etc.). Mais c'est de la spéculation. Je suis curieux de connaître une raison faisant autorité, s'il y en a une.
Lukas Eder
2
>> "Les sous-classes peuvent remplacer les méthodes déclarées synchronisées dans les super classes, supprimant ainsi la synchronisation" ... uniquement si elles n'appellent pas, superce qui nécessite une réimplémentation complète et un accès possible aux membres privés. D'ailleurs, il y a une raison pour laquelle ces méthodes sont appelées «défenseurs» - elles sont présentes pour permettre d'ajouter plus facilement de nouvelles méthodes.
bestsss

Réponses:

260

Alors qu'au début, il peut sembler évident que l'on voudrait prendre en charge le synchronizedmodificateur sur les méthodes par défaut, il s'avère que cela serait dangereux et donc interdit.

Les méthodes synchronisées sont un raccourci pour une méthode qui se comporte comme si le corps entier était enfermé dans un synchronizedbloc dont l'objet de verrouillage est le récepteur. Il peut sembler judicieux d'étendre également cette sémantique aux méthodes par défaut; après tout, ce sont aussi des méthodes d'instance avec un récepteur. (Notez que les synchronizedméthodes sont entièrement une optimisation syntaxique; elles ne sont pas nécessaires, elles sont juste plus compactes que le synchronizedbloc correspondant . causer plus de problèmes qu'ils n'en résolvent, mais ce navire a navigué il y a longtemps.)

Alors, pourquoi sont-ils dangereux? La synchronisation concerne le verrouillage. Le verrouillage consiste à coordonner l'accès partagé à un état mutable. Chaque objet doit avoir une politique de synchronisation qui détermine quels verrous gardent quelles variables d'état. (Voir Java Concurrency en pratique , section 2.4.)

De nombreux objets utilisent comme politique de synchronisation le modèle de moniteur Java (JCiP 4.1), dans lequel l'état d'un objet est protégé par son verrou intrinsèque. Il n'y a rien de magique ou de spécial dans ce modèle, mais c'est pratique, et l'utilisation du synchronizedmot - clé sur les méthodes suppose implicitement ce modèle.

C'est la classe qui possède l'état qui détermine la politique de synchronisation de cet objet. Mais les interfaces ne possèdent pas l'état des objets dans lesquels elles sont mélangées. Donc, l'utilisation d'une méthode synchronisée dans une interface suppose une politique de synchronisation particulière, mais que vous n'avez aucune base raisonnable pour supposer, il pourrait donc bien être le cas que l'utilisation de la synchronisation n'offre aucune sécurité supplémentaire sur les threads (vous synchronisez peut-être sur le mauvais verrou). Cela vous donnerait le faux sentiment de confiance que vous avez fait quelque chose à propos de la sécurité des threads, et aucun message d'erreur ne vous indique que vous supposez la mauvaise politique de synchronisation.

Il est déjà assez difficile de maintenir de manière cohérente une politique de synchronisation pour un seul fichier source; il est encore plus difficile de s'assurer qu'une sous-classe adhère correctement à la politique de synchronisation définie par sa super-classe. Essayer de le faire entre de telles classes à couplage lâche (une interface et les nombreuses classes qui l'implémentent) serait presque impossible et très sujet aux erreurs.

Compte tenu de tous ces arguments contre, quel serait l'argument? Il semble qu'ils visent principalement à faire en sorte que les interfaces se comportent davantage comme des traits. Bien que ce soit un désir compréhensible, le centre de conception pour les méthodes par défaut est l'évolution de l'interface, pas "Traits--". Là où les deux pouvaient être atteints de manière cohérente, nous nous sommes efforcés de le faire, mais là où l'un est en conflit avec l'autre, nous avons dû choisir en faveur de l'objectif de conception principal.

Brian Goetz
la source
26
Notez également que dans JDK 1.1, le synchronizedmodificateur de méthode est apparu dans la sortie javadoc, induisant les gens en erreur en leur faisant croire qu'il faisait partie de la spécification. Cela a été corrigé dans JDK 1.2. Même s'il apparaît sur une méthode publique, le synchronizedmodificateur fait partie de l'implémentation, pas du contrat. (Un raisonnement et un traitement similaires ont été nativeappliqués au modificateur.)
Stuart Marks
15
Une erreur courante dans les premiers programmes Java était de saupoudrer suffisamment synchronized composants sûrs threads et vous disposiez d'un programme presque sûr pour les threads. Le problème était que cela fonctionnait généralement bien, mais il s'est cassé de manière surprenante et fragile. Je suis d'accord que la compréhension du fonctionnement de votre verrouillage est la clé d'une application robuste.
Peter Lawrey
10
@BrianGoetz Très bonne raison. Mais pourquoi est synchronized(this) {...}autorisé dans une defaultméthode? (Comme le montre la question de Lukas.) Cela ne permet-il pas à la méthode par défaut de posséder également l'état de la classe d'implémentation? Ne voulons-nous pas empêcher cela aussi? Aurons-nous besoin d'une règle FindBugs pour trouver les cas pour lesquels des développeurs non informés le font?
Geoffrey De Smet
17
@Geoffrey: Non, il n'y a aucune raison de restreindre cela (bien qu'il doive toujours être utilisé avec précaution.) Le bloc de synchronisation nécessite que l'auteur sélectionne explicitement un objet verrou; cela leur permet de participer à la politique de synchronisation d'un autre objet, s'ils savent ce qu'est cette politique. La partie dangereuse suppose que la synchronisation sur «ceci» (ce que font les méthodes de synchronisation) est réellement significative; cela doit être une décision plus explicite. Cela dit, je m'attends à ce que les blocs de synchronisation dans les méthodes d'interface soient assez rares.
Brian Goetz
6
@GeoffreyDeSmet: Pour la même raison, vous pouvez faire par exemple synchronized(vector). Si vous voulez être en sécurité, vous ne devez jamais utiliser un objet public (tel que thislui-même) pour le verrouillage.
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();
}
}

résultat:

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

(désolé d'avoir utilisé la classe parent comme exemple)

d'après le résultat, nous pourrions savoir que le verrou de classe parent appartient à chaque sous-classe, les objets SonSync1 et SonSync2 ont un verrou d'objet différent. chaque serrure est indépendante. donc dans ce cas, je pense qu'il n'est pas dangereux d'utiliser un synchronisé dans une classe parent ou une interface commune. quelqu'un pourrait-il expliquer plus à ce sujet?

zhenke zhu
la source