¿Cuál es la razón por la que no se permite "sincronizado" en los métodos de interfaz de Java 8?
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 synchronized
modificador en declaraciones de métodos:
interface Interface2 {
default synchronized void method1() {
// ^^^^^^^^^^^^ Modifier 'synchronized' not allowed here
}
static synchronized void method2() {
// ^^^^^^^^^^^^ Modifier 'synchronized' not allowed here
}
}
Ahora bien, se puede argumentar que las dos interfaces se comportan de la misma manera excepto que Interface2
se establece un contrato una method1()
y otra vez method2()
, que es un poco más fuerte que lo que Interface1
hace. Por supuesto, también podríamos argumentar que default
las implementaciones no deberían hacer suposiciones sobre el estado concreto de la implementación, o que esa palabra clave simplemente no cumpliría su función.
Pregunta:
¿Cuál es la razón por la que el grupo de expertos JSR-335 decidió no dar soporte synchronized
a los métodos de interfaz?
Si bien al principio podría parecer obvio que uno querría admitir el synchronized
modificador en los métodos predeterminados, resulta que hacerlo sería peligroso y, por lo tanto, estaba prohibido.
Los métodos sincronizados son una abreviatura de un método que se comporta como si todo el cuerpo estuviera encerrado en un synchronized
bloque cuyo objeto de bloqueo es el receptor. Podría parecer sensato extender también esta semántica a los métodos predeterminados; después de todo, también son métodos de instancia con un receptor. (Tenga en cuenta que synchronized
los métodos son completamente una optimización sintáctica; no son necesarios, simplemente son más compactos que el synchronized
bloque correspondiente. Se puede argumentar razonablemente que, en primer lugar, se trató de una optimización sintáctica prematura 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 consiste en 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. (Ver Concurrencia de Java en la práctica , sección 2.4.)
Muchos objetos utilizan como política de sincronización el patrón Java Monitor (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 synchronized
palabra clave en los métodos asume implícitamente este patrón.
Es la clase propietaria del estado la 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 ninguna base razonable para asumir, por lo que bien podría darse el caso de que el uso de la sincronización no proporciona ningún tipo de seguridad adicional para los subprocesos (es posible que esté sincronizando con el bloqueo incorrecto). Esto le daría la falsa sensación de confianza de que ha hecho algo con respecto a la seguridad de los subprocesos y ningún mensaje de error le indicará 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 único archivo fuente; es aún más difícil garantizar que una subclase cumpla correctamente la política de sincronización definida por su superclase. Intentar hacerlo entre clases tan poco acopladas (una interfaz y posiblemente muchas clases que la implementan) sería casi imposible y muy propenso a errores.
Teniendo en cuenta todos esos argumentos en contra, ¿cuál sería el argumento a favor? 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 de los métodos predeterminados es la evolución de la interfaz, no "Rasgos--". Cuando los dos podían lograrse de manera consistente, nos esforzamos por hacerlo, pero cuando uno está en conflicto con el otro, tuvimos que elegir a favor del objetivo de diseño principal.