Java Thread aparentemente sin esperar sentencia condicional

Adam Jarvis:

Para una biblioteca reciente que estoy escribiendo, me escribió un hilo que los bucles de forma indefinida. En este bucle, comienzo con una sentencia condicional comprobación de una propiedad en el objeto roscado. Sin embargo, parece que cualquiera que sea el valor inicial de la propiedad tiene, será lo que se vuelve incluso después de ser actualizado.

A menos que hago algún tipo de interrupción como Thread.sleepo una declaración de impresión.

No estoy muy seguro de cómo hacer la pregunta por desgracia. De lo contrario, estaría buscando en la documentación de Java. He reducía el código a un ejemplo mínimo que explica el problema en términos simples.

public class App {
    public static void main(String[] args) {
        App app = new App();
    }

    class Test implements Runnable {

        public boolean flag = false;

        public void run() {
            while(true) {

                // try {
                //     Thread.sleep(1);
                // } catch (InterruptedException e) {}

                if (this.flag) {
                    System.out.println("True");
                }
            }
        }

    }

    public App() {
        Test t = new Test();
        Thread thread = new Thread(t);
        System.out.println("Starting thread");
        thread.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {}

        t.flag = true;
        System.out.println("New flag value: " + t.flag);
    }
}

Ahora, yo presumiría que después se cambia el valor de la flagpropiedad en el hilo conductor, que iba a ver de inmediato las masas de 'True' escupiendo al terminal. Sin embargo, no lo hacemos ..

Si anulo la observación de las Thread.sleeplíneas dentro del bucle de hilo, el programa funciona como se esperaba y vemos las muchas líneas de 'True' están impresas después de que cambiamos el valor en el Appobjeto. Como una adición, cualquier método de impresión en lugar del Thread.sleeptambién funciona, pero algo de código simple asignación no lo hace. Supongo que esto se debe a que se saca como el código anti-usado en tiempo de compilación.

Por lo tanto, mi pregunta es realmente: ¿Por qué tengo que usar algún tipo de interrupción para obtener el hilo para comprobar las condiciones correctamente?

Stephen C:

Por lo tanto, mi pregunta es realmente: ¿Por qué tengo que usar algún tipo de interrupción para obtener el hilo para comprobar las condiciones correctamente?

Así que no tiene que . Hay al menos dos formas de implementar este ejemplo particular sin el uso de "interrupción".

  • Si se declara flaga ser volatile, entonces todo funcionará bien.
  • Será también el trabajo si se declara flagser private, escritura synchronizedmétodos get y set, y el uso de los de todos los accesos.

    public class App {
        public static void main(String[] args) {
            App app = new App();
        }
    
        class Test implements Runnable {
            private boolean flag = false;
    
            public synchronized boolean getFlag() {
                return this.flag;
            }
    
            public synchronized void setFlag(boolean flag) {
                return this.flag = flag;
            }
    
            public void run() {
                while(true) {
                    if (this.getFlag()) {   // Must use the getter here too!
                        System.out.println("True");
                    }
                }
            }
        }
    
        public App() {
            Test t = new Test();
            Thread thread = new Thread(t);
            System.out.println("Starting thread");
            thread.start();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {}
    
            t.setFlag(true);
            System.out.println("New flag value: " + t.getFlag());
    }
    

Pero ¿por qué se necesita para hacer esto?

Porque a menos que usted use un volatileo synchronized(y utiliza synchronizedcorrectamente) y luego un hilo no está garantizada para ver la memoria los cambios realizados por otro hilo.

En su ejemplo, el hilo niño no ve el valor hasta a la fecha de flag. (No es que las condiciones en sí son incorrectos o "no trabajo". En realidad, están recibiendo entradas obsoletas. Se trata de "basura, sale basura".)

La especificación del lenguaje Java establece con precisión las condiciones en las que se garantiza un hilo para ver (anterior) escrituras realizadas por otro subproceso. Esta parte de la especificación se llama el modelo de memoria de Java, y es en JLS 17,4 . Hay una más fácil de entender la explicación en Java concurrencia en la práctica por Brian Goetz et al.

Tenga en cuenta que el comportamiento inesperado podría ser debido a la decisión de JIT mantener la bandera en un registro. También podría ser que el compilador JIT ha decidido que no necesita la memoria caché de escritura simultánea fuerza, etcétera. (El compilador JIT no quiere forzar escritura simultánea en cada escritura en la memoria de todos los campos. Eso sería un golpe importante el rendimiento en sistemas multi-core ... que las máquinas más modernas son.)


El mecanismo de interrupción de Java es otra manera de lidiar con esto. No necesita ninguna sincronización debido a que el método llama a eso. Además, la interrupción funcionará cuando el hilo que está tratando de interrupción está esperando actualmente o se bloquea en una operación interrumpible; por ejemplo, en una Object::waitllamada.

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=332339&siteId=1
Recomendado
Clasificación