Qual è il motivo per cui "sincronizzato" non è consentito nei metodi di interfaccia Java 8?


210

In Java 8, posso facilmente scrivere:

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

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

Otterrò la semantica della sincronizzazione completa che posso usare anche in classe. Non posso, tuttavia, utilizzare il synchronizedmodificatore nelle dichiarazioni dei metodi:

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

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

Ora, si può sostenere che le due interfacce si comportano allo stesso modo, tranne che Interface2stabilisce un contratto su method1()e su method2(), che è un po 'più forte di quello che Interface1fa. Naturalmente, potremmo anche sostenere che le defaultimplementazioni non dovrebbero fare ipotesi su uno stato di implementazione concreto, o che una tale parola chiave semplicemente non trarrebbe il suo peso.

Domanda:

Qual è il motivo per cui il gruppo di esperti JSR-335 ha deciso di non supportare synchronizedmetodi di interfaccia?


1
La sincronizzazione è un comportamento di implementazione e modifica il risultato del codice byte finale creato dal compilatore in modo che possa essere utilizzato accanto a un codice. Non ha senso nella dichiarazione del metodo. Dovrebbe confondere ciò che ha prodotto il compilatore se sincronizzato è sul livello di astrazione.
Martin Strejc,

@MartinStrejc: questa potrebbe essere una spiegazione per omettere default synchronized, ma non necessariamente per static synchronized, anche se accetterei che quest'ultimo potrebbe essere stato omesso per motivi di coerenza.
Lukas Eder,

1
Non sono sicuro che questa domanda aggiunga alcun valore poiché il synchronizedmodificatore può essere sostituito nelle sottoclassi, quindi importerebbe solo se esistesse qualcosa come metodi predefiniti finali. (L'altra tua domanda)
Skiwi

@skiwi: l'argomento prevalente non è sufficiente. Le sottoclassi possono ignorare i metodi dichiarati synchronizedin superclassi, rimuovendo efficacemente la sincronizzazione. Non sarei sorpreso dal fatto che non sostenere synchronizede non supportare finalsia correlato, forse, forse a causa dell'eredità multipla (ad esempio ereditare void x() e synchronized void x() , ecc.). Ma questa è una speculazione. Sono curioso di sapere un motivo autorevole, se ce n'è uno.
Lukas Eder,

2
>> "Le sottoclassi possono ignorare i metodi dichiarati sincronizzati in superclassi, rimuovendo efficacemente la sincronizzazione" ... solo se non chiamano, il superche richiede una reimplementazione completa e un possibile accesso ai membri privati. A proposito, c'è una ragione per cui questi metodi sono chiamati "difensori" - sono presenti per consentire più facilmente l'aggiunta di nuovi metodi.
bestsss,

Risposte:


260

Mentre all'inizio potrebbe sembrare ovvio che si vorrebbe supportare il synchronizedmodificatore sui metodi predefiniti, si scopre che farlo sarebbe pericoloso e quindi è stato proibito.

I metodi sincronizzati sono una scorciatoia per un metodo che si comporta come se l'intero corpo fosse racchiuso in un synchronizedblocco il cui oggetto di blocco è il ricevitore. Potrebbe sembrare ragionevole estendere questa semantica anche ai metodi predefiniti; dopo tutto, sono anche metodi di istanza con un ricevitore. (Si noti che i synchronizedmetodi sono interamente un'ottimizzazione sintattica; non sono necessari, sono solo più compatti del synchronizedblocco corrispondente . C'è un ragionevole argomento da sostenere che si trattava in primo luogo di un'ottimizzazione sintattica prematura e che i metodi sincronizzati causa più problemi di quanti ne risolvano, ma quella nave navigò molto tempo fa.)

Quindi, perché sono pericolosi? La sincronizzazione riguarda il blocco. Il blocco riguarda il coordinamento dell'accesso condiviso allo stato mutabile. Ogni oggetto dovrebbe avere una politica di sincronizzazione che determina quali blocchi proteggono quali variabili di stato. (Vedi Concorrenza Java in pratica , sezione 2.4.)

Molti oggetti utilizzano come criterio di sincronizzazione Java Monitor Pattern (JCiP 4.1), in cui lo stato di un oggetto è protetto dal suo blocco intrinseco. Non c'è nulla di magico o speciale in questo schema, ma è conveniente, e l'uso della synchronizedparola chiave sui metodi presuppone implicitamente questo schema.

È la classe che possiede lo stato che arriva a determinare la politica di sincronizzazione di quell'oggetto. Ma le interfacce non possiedono lo stato degli oggetti in cui sono mescolati. Quindi, l'utilizzo di un metodo sincronizzato in un'interfaccia presuppone una particolare politica di sincronizzazione, ma che non si ha ragionevolmente presumere, quindi potrebbe essere il caso l'uso della sincronizzazione non fornisce alcuna sicurezza aggiuntiva del thread (è possibile che si stia sincronizzando sul blocco errato). Questo ti darebbe il falso senso di fiducia che hai fatto qualcosa sulla sicurezza del thread e nessun messaggio di errore ti dice che stai assumendo la politica di sincronizzazione errata.

È già abbastanza difficile mantenere costantemente una politica di sincronizzazione per un singolo file sorgente; è ancora più difficile garantire che una sottoclasse aderisca correttamente alla politica di sincronizzazione definita dalla sua superclasse. Cercare di farlo tra classi così vagamente accoppiate (un'interfaccia e le possibilmente molte classi che la implementano) sarebbe quasi impossibile e altamente soggetto a errori.

Alla luce di tutti questi argomenti contrari, per quale motivo? Sembra che si tratti principalmente di far sì che le interfacce si comportino più come tratti. Mentre questo è un desiderio comprensibile, il centro di progettazione per i metodi predefiniti è l'evoluzione dell'interfaccia, non "Tratti--". Laddove i due potevano essere costantemente raggiunti, ci siamo sforzati di farlo, ma laddove uno è in conflitto con l'altro, abbiamo dovuto scegliere a favore dell'obiettivo di progettazione principale.


26
Si noti inoltre che in JDK 1.1, il synchronizedmodificatore del metodo è apparso nell'output javadoc, inducendo le persone a indurre in errore che faceva parte delle specifiche. Questo problema è stato risolto in JDK 1.2. Anche se appare su un metodo pubblico, il synchronizedmodificatore fa parte dell'implementazione, non del contratto. (Ragionamento e trattamento simili si sono verificati per il nativemodificatore.)
Stuart Marks

15
Un errore comune nei primi programmi Java era quello di cospargere abbastanza synchronizede infilare i componenti sicuri in giro e si aveva un programma quasi sicuro. Il problema era che di solito funzionava bene ma si era rotto in modi sorprendenti e fragili. Sono d'accordo che capire come funziona il tuo blocco è la chiave per applicazioni robuste.
Peter Lawrey,

10
@BrianGoetz Ottimo motivo. Ma perché è synchronized(this) {...}consentito in un defaultmetodo? (Come mostrato nella domanda di Lukas.) Questo non consente anche al metodo predefinito di possedere lo stato della classe di implementazione? Non vogliamo impedire anche quello? Avremo bisogno di una regola FindBugs per trovare i casi per i quali gli sviluppatori disinformati lo fanno?
Geoffrey De Smet,

17
@Geoffrey: No, non c'è motivo di limitare questo (sebbene debba sempre essere usato con cura.) Il blocco di sincronizzazione richiede all'autore di selezionare esplicitamente un oggetto di blocco; questo consente loro di partecipare alla politica di sincronizzazione di qualche altro oggetto, se sanno cos'è quella politica. La parte pericolosa è presumere che la sincronizzazione su "questo" (che è ciò che fanno i metodi di sincronizzazione) sia effettivamente significativa; questa deve essere una decisione più esplicita. Detto questo, mi aspetto che i blocchi di sincronizzazione nei metodi di interfaccia siano piuttosto rari.
Brian Goetz,

6
@GeoffreyDeSmet: per lo stesso motivo puoi fare ad es synchronized(vector). Se vuoi essere sicuro, non dovresti mai usare un oggetto pubblico (come se thisstesso) per bloccare.
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();
}
}

risultato:

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

(scusate per aver usato la classe genitore come esempio)

dal risultato, potremmo sapere che il blocco della classe genitore appartiene a ogni sottoclasse, gli oggetti SonSync1 e SonSync2 hanno un blocco oggetti diverso. ogni blocco è indipendenza. quindi in questo caso, penso che non sia pericoloso usare un sincronizzato in una classe genitore o un'interfaccia comune. qualcuno potrebbe spiegare di più su questo?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.