¿Cuál es la razón por la cual "sincronizado" no está permitido en los métodos de interfaz Java 8?

210

En Java 8, puedo escribir fácilmente:

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

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

Obtendré la semántica de sincronización completa que puedo usar también en las clases. Sin embargo, no puedo usar el synchronizedmodificador en las declaraciones de método:

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

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

Ahora, se puede argumentar que las dos interfaces se comportan de la misma manera excepto que Interface2establece un contrato en method1()y en method2(), que es un poco más fuerte que lo Interface1hace. Por supuesto, también podríamos argumentar que las defaultimplementaciones no deberían hacer suposiciones sobre el estado de implementación concreto, o que dicha palabra clave simplemente no aumentaría su peso.

Pregunta:

¿Cuál es la razón por la cual el grupo de expertos JSR-335 decidió no admitir los synchronizedmétodos de interfaz?

Lukas Eder
fuente
1
Sincronizado es un comportamiento de implementación y cambia el resultado final del código de bytes realizado por el compilador para que pueda usarse junto a un código. No tiene sentido en la declaración del método. Debería ser confuso lo que produce el compilador si sincronizado está en la capa de abstracción.
Martin Strejc
@MartinStrejc: Esa podría ser una explicación para omitir default synchronized, pero no necesariamente static synchronized, aunque aceptaría que esta última podría haberse omitido por razones de coherencia.
Lukas Eder
1
No estoy seguro de si esta pregunta agrega algún valor ya que el synchronizedmodificador puede ser anulado en las subclases, por lo tanto, solo importaría si hubiera algo como métodos predeterminados finales. (Su otra pregunta)
skiwi
@skiwi: El argumento principal no es suficiente. Las subclases pueden anular los métodos que se declaran synchronizeden superclases, eliminando efectivamente la sincronización. Sin embargo, no me sorprendería que no admitir synchronizedy no admitir finalesté relacionado, tal vez debido a la herencia múltiple (por ejemplo, herencia void x() y synchronized void x() , etc.). Pero eso es especulación. Tengo curiosidad acerca de una razón autorizada, si hay una.
Lukas Eder
2
>> "Las subclases pueden anular los métodos que se declaran sincronizados en superclases, eliminando efectivamente la sincronización" ... solo si no llaman, lo superque requiere una reimplementación completa y un posible acceso a miembros privados. Por cierto, hay una razón por la cual esos métodos se llaman "defensores": están presentes para permitir la adición de nuevos métodos más fácilmente.
Bestsss

Respuestas:

260

Si bien al principio puede parecer obvio que uno desearía admitir el synchronizedmodificador en los métodos predeterminados, resulta que hacerlo sería peligroso, por lo que estaba prohibido.

Los métodos sincronizados son una forma abreviada de un método que se comporta como si todo el cuerpo estuviera encerrado en un synchronizedbloque cuyo objeto de bloqueo es el receptor. Puede parecer sensato extender esta semántica a los métodos predeterminados también; después de todo, también son métodos de instancia con un receptor. (Tenga en cuenta que los synchronizedmétodos son completamente una optimización sintáctica; no son necesarios, simplemente son más compactos que el synchronizedbloque correspondiente . Hay un argumento razonable para afirmar que esta fue una optimización sintáctica prematura en primer lugar, y que los métodos sincronizados causan más problemas de los que resuelven, pero ese barco zarpó hace mucho tiempo).

Entonces, ¿por qué son peligrosos? La sincronización se trata de bloquear. El bloqueo se trata de coordinar el acceso compartido al estado mutable. Cada objeto debe tener una política de sincronización que determine qué bloqueos protegen qué variables de estado. (Consulte Concurrencia de Java en la práctica , sección 2.4.)

Muchos objetos utilizan como política de sincronización el patrón de monitor de Java (JCiP 4.1), en el que el estado de un objeto está protegido por su bloqueo intrínseco. No hay nada mágico o especial en este patrón, pero es conveniente, y el uso de la synchronizedpalabra clave en los métodos asume implícitamente este patrón.

Es la clase propietaria del estado que determina la política de sincronización de ese objeto. Pero las interfaces no poseen el estado de los objetos en los que se mezclan. Por lo tanto, el uso de un método sincronizado en una interfaz supone una política de sincronización particular, pero una que no tiene una base razonable para asumir, por lo que bien podría ser el caso de que El uso de la sincronización no proporciona seguridad adicional para subprocesos (es posible que esté sincronizando en el bloqueo incorrecto). Esto le daría la falsa sensación de confianza de que ha hecho algo sobre la seguridad de los subprocesos, y ningún mensaje de error le indica que está asumiendo una política de sincronización incorrecta.

Ya es bastante difícil mantener consistentemente una política de sincronización para un solo archivo fuente; es aún más difícil asegurarse de que una subclase se adhiera correctamente a la política de sincronización definida por su superclase. Intentar hacerlo entre esas clases poco acopladas (una interfaz y posiblemente las muchas clases que lo implementan) sería casi imposible y altamente propenso a errores.

Teniendo en cuenta todos esos argumentos en contra, ¿cuál sería el argumento? Parece que se trata principalmente de hacer que las interfaces se comporten más como rasgos. Si bien este es un deseo comprensible, el centro de diseño para los métodos predeterminados es la evolución de la interfaz, no "Rasgos-". Donde los dos podrían lograrse de manera consistente, nos esforzamos por hacerlo, pero donde uno está en conflicto con el otro, tuvimos que elegir a favor del objetivo de diseño primario.

Brian Goetz
fuente
26
Tenga en cuenta también que en JDK 1.1, el synchronizedmodificador de método apareció en la salida de javadoc, engañando a la gente a pensar que era parte de la especificación. Esto se solucionó en JDK 1.2. Incluso si aparece en un método público, el synchronizedmodificador es parte de la implementación, no del contrato. (Razonamiento y tratamiento similares ocurrieron para el nativemodificador).
Stuart Marks,
15
Un error común en los primeros programas de Java fue rociar suficiente synchronizedy usar componentes seguros para subprocesos y usted tenía un programa casi seguro para subprocesos. El problema era que esto generalmente funcionaba bien, pero se rompió de manera sorprendente y frágil. Estoy de acuerdo en que entender cómo funciona su bloqueo es clave para aplicaciones robustas.
Peter Lawrey
10
@BrianGoetz Muy buena razón. Pero, ¿por qué está synchronized(this) {...}permitido en un defaultmétodo? (Como se muestra en la pregunta de Lukas). ¿Eso no permite que el método predeterminado también sea dueño del estado de la clase de implementación? ¿No queremos evitar eso también? ¿Necesitaremos una regla FindBugs para encontrar los casos en que los desarrolladores desinformados hacen eso?
Geoffrey De Smet
17
@ Geoffrey: No, no hay razón para restringir esto (aunque siempre debe usarse con cuidado). El bloque de sincronización requiere que el autor seleccione explícitamente un objeto de bloqueo; esto les permite participar en la política de sincronización de algún otro objeto, si saben cuál es esa política. La parte peligrosa es asumir que la sincronización en 'esto' (que es lo que hacen los métodos de sincronización) es realmente significativa; Esto debe ser una decisión más explícita. Dicho esto, espero que los bloques de sincronización en los métodos de interfaz sean bastante raros.
Brian Goetz
66
@GeoffreyDeSmet: Por la misma razón que puede hacer, por ejemplo synchronized(vector). Si desea estar seguro, nunca debe usar un objeto público (como él thismismo) para bloquear.
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();
}
}

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

(perdón por usar la clase padre como ejemplo)

a partir del resultado, podríamos saber que el bloqueo de la clase principal es propiedad de cada subclase, SonSync1 y SonSync2 tienen un bloqueo de objeto diferente. cada cerradura es independencia. así que en este caso, creo que no es peligroso usar un sincronizado en una clase principal o una interfaz común. ¿Alguien podría explicar más sobre esto?

zhenke zhu
fuente