Qual é a razão pela qual “sincronizado” não é permitido nos métodos de interface Java 8?

210

No Java 8, eu posso escrever facilmente:

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

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

Vou obter a semântica de sincronização completa que também posso usar nas aulas. No entanto, não posso usar o synchronizedmodificador nas declarações do método:

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

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

Agora, pode-se argumentar que as duas interfaces comportam da mesma maneira, exceto que Interface2estabelece um contrato no method1()e no method2(), que é um pouco mais forte do que o que Interface1faz. Obviamente, também podemos argumentar que as defaultimplementações não devem fazer suposições sobre o estado concreto da implementação, ou que essa palavra-chave simplesmente não daria seu peso.

Questão:

Qual é a razão pela qual o grupo de especialistas JSR-335 decidiu não oferecer suporte synchronizednos métodos de interface?

Lukas Eder
fonte
1
Sincronizado é um comportamento de implementação e altera o resultado final do código de bytes feito pelo compilador para que possa ser usado ao lado de um código. Não faz sentido na declaração do método. Deve ser confuso o que o compilador produz se sincronizado estiver na camada de abstração.
Martin Strejc
@MartinStrejc: Essa pode ser uma explicação para a omissão default synchronized, mas não necessariamente para static synchronized, embora eu aceite que o último possa ter sido omitido por motivos de consistência.
Lukas Eder
1
Não tenho certeza se esta pergunta agrega algum valor, pois o synchronizedmodificador pode ser substituído nas subclasses, portanto, só importaria se houvesse algo como métodos padrão finais. (Sua outra pergunta)
skiwi
@skiwi: O argumento principal não é suficiente. As subclasses podem substituir os métodos declarados synchronizednas superclasses, removendo efetivamente a sincronização. Não me surpreenderia que não apoiar synchronizede não apoiar finalesteja relacionado, talvez por causa de herança múltipla (por exemplo, herança void x() e synchronized void x() etc.). Mas isso é especulação. Estou curioso sobre uma razão autorizada, se houver uma.
Lukas Eder #
2
>> "As subclasses podem substituir métodos declarados sincronizados em super classes, removendo efetivamente a sincronização" ... somente se não chamarem super que requer uma reimplementação completa e possível acesso a membros privados. Aliás, há uma razão para que esses métodos sejam chamados de "defensores" - eles estão presentes para facilitar a adição de novos métodos.
bestsss 10/05

Respostas:

260

Embora a princípio possa parecer óbvio que alguém queira apoiar o synchronized modificador nos métodos padrão, acontece que isso seria perigoso e, portanto, era proibido.

Métodos sincronizados são uma abreviação de um método que se comporta como se todo o corpo estivesse encerrado em um synchronizedbloco cujo objeto de bloqueio é o receptor. Pode parecer sensato estender essa semântica para métodos padrão também; afinal, eles também são métodos de instância com um receptor. (Observe que os synchronizedmétodos são inteiramente uma otimização sintática; eles não são necessários, são apenas mais compactos que os correspondentessynchronized bloco . Há um argumento razoável a ser feito de que essa foi uma otimização sintática prematura em primeiro lugar e que métodos sincronizados causam mais problemas do que resolvem, mas esse navio navegou há muito tempo.)

Então, por que eles são perigosos? Sincronização é sobre bloqueio. O bloqueio é sobre a coordenação do acesso compartilhado ao estado mutável. Cada objeto deve ter uma política de sincronização que determine quais bloqueios guardam quais variáveis ​​de estado. (Consulte Concorrência Java na prática , seção 2.4.)

Muitos objetos usam como política de sincronização o Java Monitor Pattern (JCiP 4.1), no qual o estado de um objeto é protegido por seu bloqueio intrínseco. Não há nada de mágico ou especial nesse padrão, mas é conveniente, e o uso da synchronizedpalavra-chave em métodos assume implicitamente esse padrão.

É a classe que possui o estado que determina a política de sincronização desse objeto. Mas as interfaces não possuem o estado dos objetos nos quais eles estão misturados. Portanto, o uso de um método sincronizado em uma interface pressupõe uma política de sincronização específica, mas que você não tem base razoável para supor; portanto, pode ser que o uso da sincronização não fornece segurança adicional ao thread (você pode estar sincronizando na trava errada). Isso lhe daria a falsa sensação de confiança de que você fez algo sobre segurança de encadeamento, e nenhuma mensagem de erro informa que você está assumindo a política de sincronização errada.

Já é bastante difícil manter consistentemente uma política de sincronização para um único arquivo de origem; é ainda mais difícil garantir que uma subclasse adira corretamente à política de sincronização definida por sua superclasse. Tentar fazê-lo entre essas classes fracamente acopladas (uma interface e as possivelmente muitas classes que a implementam) seria quase impossível e altamente suscetível a erros.

Dados todos esses argumentos contra, qual seria o argumento? Parece que a maior parte deles faz com que as interfaces se comportem mais como características. Embora esse seja um desejo compreensível, o centro de design para métodos padrão é a evolução da interface, não "Traits--". Onde os dois poderiam ser consistentemente alcançados, nos esforçamos para fazê-lo, mas onde um está em conflito com o outro, tivemos que escolher a favor do objetivo principal do design.

Brian Goetz
fonte
26
Observe também que no JDK 1.1, o synchronizedmodificador de método apareceu na saída do javadoc, levando as pessoas a pensarem que era parte da especificação. Isso foi corrigido no JDK 1.2. Mesmo que apareça em um método público, o synchronizedmodificador faz parte da implementação, não do contrato. (Raciocínio e tratamento similar ocorreu para o nativemodificador.)
Stuart marcas
15
Um erro comum nos primeiros programas Java era borrifar o suficiente synchronizede encadear componentes seguros e você tinha um programa quase encadeado. O problema era que isso geralmente funcionava bem, mas era quebrado de maneiras surpreendentes e quebradiças. Concordo que entender como o bloqueio funciona é a chave para aplicativos robustos.
Peter Lawrey
10
@BrianGoetz Muito bom motivo. Mas por que é synchronized(this) {...}permitido em um defaultmétodo? (Como mostrado na pergunta de Lukas.) Isso não permite que o método padrão seja o proprietário do estado da classe de implementação também? Não queremos impedir isso também? Precisamos de uma regra FindBugs para encontrar os casos pelos quais desenvolvedores desinformados fazem isso?
Geoffrey De Smet
17
@ Geoffrey: Não, não há razão para restringir isso (embora deva sempre ser usado com cuidado.) O bloco de sincronização exige que o autor selecione explicitamente um objeto de bloqueio; isso permite que eles participem da política de sincronização de algum outro objeto, se souberem qual é essa política. A parte perigosa é supor que a sincronização com 'this' (que é o que os métodos de sincronização fazem) é realmente significativa; isso precisa ser uma decisão mais explícita. Dito isto, espero que os blocos de sincronização nos métodos de interface sejam bastante raros.
Brian Goetz 5/05
6
@ GeoffreyDeSmet: Pela mesma razão, você pode fazer, por exemplo synchronized(vector). Se você quer ser seguro, nunca deve usar um objeto público (como thisele mesmo) para bloquear.
Yogu 5/05
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();
}
}

resultado:

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

(desculpe por usar a classe pai como exemplo)

do resultado, poderíamos saber que o bloqueio da classe pai pertence a todas as subclasses, os objetos SonSync1 e SonSync2 têm um bloqueio de objeto diferente. todo bloqueio é independência. Portanto, nesse caso, acho que não é perigoso usar um sincronizado em uma classe pai ou em uma interface comum. alguém poderia explicar mais sobre isso?

zhenke zhu
fonte