web-dev-qa-db-fra.com

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

Dans Java 8, je peux facilement écrire:

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

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

Je vais obtenir la sémantique complète de la synchronisation que je peux utiliser également dans les classes. Je ne peux cependant pas utiliser le modificateur synchronized 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 soutenir que les deux interfaces se comportent de la même manière, sauf que Interface2 Établit un contrat sur method1() et sur method2(), qui est un peu plus fort que ce que Interface1 fait. Bien sûr, nous pourrions également affirmer que les implémentations de default ne doivent pas émettre d'hypothèses sur leur état d'implémentation concrète, ou qu'un tel mot clé ne tirerait tout simplement pas son poids.

Question:

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

208
Lukas Eder

Bien que, au début, il puisse sembler évident de vouloir supporter le modificateur synchronized 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 bloc synchronized dont l'objet lock était le destinataire. Il pourrait sembler judicieux d'étendre cette sémantique aux méthodes par défaut également; après tout, ce sont aussi des méthodes d'instance avec un récepteur. (Notez que les méthodes synchronized sont entièrement une optimisation syntaxique; elles ne sont pas nécessaires, elles sont juste plus compactes que le bloc synchronized correspondant. Il existe un argument raisonnable l'optimisation syntaxique prématurée en premier lieu, et que les méthodes synchronisées posent plus de problèmes qu'elles n'en résolvent, mais que 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 protègent quelles variables d'état. (Voir Java Concurrency in Practice , section 2.4.)

De nombreux objets utilisent comme stratégie 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. Ce modèle n’a rien de magique ni de spécial, mais il est pratique et l’utilisation du mot clé synchronized sur des méthodes suppose implicitement ce modèle.

C'est la classe qui possède l'état qui doit déterminer la stratégie de synchronisation de cet objet. Mais les interfaces ne possèdent pas l’état des objets dans lesquels elles sont mélangées. Ainsi, l’utilisation d’une méthode synchronisée dans une interface suppose une politique de synchronisation particulière, mais pour laquelle vous n’avez aucune base raisonnable à supposer. l'utilisation de la synchronisation n'apporte aucune sécurité supplémentaire aux threads (vous êtes peut-être en train de synchroniser 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 prenez la mauvaise stratégie de synchronisation.

Il est déjà assez difficile de maintenir de manière cohérente une stratégie de synchronisation pour un fichier source unique; 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 éventuellement) serait presque impossible et très sujet aux erreurs.

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

257
Brian Goetz
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'utiliser la classe parent comme exemple)

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

0
zhenke zhu