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?


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.


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?

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.