¿Por qué este código finaliza con éxito cuando el método get() se marca como sincronizado a pesar de que el campo value no es volátil? Sin sincronizado, se ejecuta indefinidamente en mi máquina (como se esperaba).

public class MtApp {

    private int value;

    /*synchronized*/ int get() {
        return value;
    }

    void set(int value) {
        this.value = value;
    }

    public static void main(String[] args) throws Exception {
        new MtApp().run();
    }

    private void run() throws Exception {
        Runnable r = () -> {
            while (get() == 0) ;
        };
        Thread thread = new Thread(r);
        thread.start();
        Thread.sleep(10);
        set(5);
        thread.join();
    }
}
1
Yuriy Kiselev 8 sep. 2018 a las 22:07

3 respuestas

La mejor respuesta

@Andy Turner es en parte correcta.

La adición del synchronized en el método get() está afectando los requisitos de visibilidad de la memoria y provocando que el compilador (JIT) genere un código diferente.

Sin embargo, estrictamente hablando, debe haber una relación antes de que conecte la llamada set(...) y la llamada get(). Eso significa que el método set debe ser synchronized así como get (¡si lo vas a hacer de esta manera!).

En resumen, la versión de su código que observó que no funciona NO garantiza que funcione en todas las plataformas y en todas las circunstancias. De hecho, ¡tienes suerte!


Leyendo entre líneas, parece que estás tratando de averiguar cómo funciona el modelo de memoria de Java por experimentación. Esta no es una buena idea. El problema es que está intentando realizar ingeniería inversa en un cuadro negro inmensamente complicado sin suficientes "parámetros de entrada" 1 para que pueda variar para cubrir todos los aspectos potenciales del comportamiento de las cajas negras.

Como resultado, el enfoque de "aprender por experimento" puede dejarlo con una comprensión incompleta o errónea.

Si desea una comprensión completa y precisa, debe comenzar leyendo sobre el Modelo de memoria Java en un buen libro de texto ... o el JLS mismo. Por supuesto, utilice la experimentación para intentar confirmar su comprensión, pero debe ser consciente de que el JMM especifica (garantiza) solo lo que sucede si hace lo correcto. Si hace algo incorrecto, su código puede funcionar de todos modos ... dependiendo de todo tipo de factores. Por lo tanto, a menudo es difícil obtener confirmación experimental de que una forma particular de hacer las cosas es 2 correcta o incorrecta.

1 - Algunos de los parámetros que necesitaría en realidad no existen. Por ejemplo, el que le permite ejecutar Java N para N> 12, o el que le permite ejecutar en hardware al que no tiene acceso ... o que aún no existe.

2 - Como se ilustra con su ejemplo. Está obteniendo la respuesta "correcta", aunque el código sea incorrecto.

1
Stephen C 9 sep. 2018 a las 01:29

Para empezar, value debe ser volatile o ambos get y set deben ser synchronized para que esto sea correcto.

JLS 17.4.5:

Se produce un desbloqueo en un monitor antes de cada bloqueo posterior en ese monitor.

Es posible que value se establezca en 5 antes de que se libere el bloqueo, lo que lo coloca antes del borde ocurre antes antes y lo hace disponible la próxima vez que el bloqueo es adquirido

Cabe señalar que tales garantías son frágiles y, dependiendo del planificador de subprocesos, pueden no existir en absoluto. En plataformas con modelos de sincronización más débiles, es posible que no vea los mismos efectos que ve aquí.


Ver también: El bucle no ve el valor modificado sin una declaración de impresión

Comportamiento extraño de un hilo Java asociado con System.out

0
Breeze 8 sep. 2018 a las 20:44

La sincronización obliga a this.value = value a suceder antes get().

Asegura la visibilidad del valor actualizado.

Sin sincronización, no existe tal garantía. podría funcionar, puede que no.

2
Andy Turner 8 sep. 2018 a las 19:21