La palabra clave java volátil no se comporta como se esperaba

Resuelto Albin Chang asked hace 8 meses • 2 respuestas

Experimenté para verificar la visibilidad de los volátiles.

Este es mi código de prueba:

package org.example;

// Press Shift twice to open the Search Anywhere dialog and enter `show whitespaces`,
// Then press Enter. Now you can see space characters in the code.
public class Main3 {

    private static volatile int i = 0;

    private static Object o = new Object();

    private static void add()
    {
        i++;
    }

    public static void main(String[] args) throws InterruptedException {

        //The first thread performs the write operation
        Thread th1 = new Thread( () -> {
            for (int j = 0; j < 10000; ) {


                synchronized (o)
                {
                    add();

                    o.notify();

                }


            }
        } );

        //The second thread performs the read operation
        Thread th2 = new Thread( () -> {
            for (int j = 0; j < 10000; ) {

                int before = i;

                synchronized (o)
                {
                    try {
                        o.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }

                int after = i;

                if( before == after )
                {
                    System.out.println( "before: " + before + ", after: " + after );
                }

            }
        } );


        th1.start();
        th2.start();

        th1.join();
        th2.join();

        System.out.println(i);


    }
}

Producción:

before: 1201480699, after: 1201480699
before: 1220677165, after: 1220677165
before: 1791815757, after: 1791815757
before: 1807949964, after: 1807949964
before: 1940818428, after: 1940818428
before: -1687188478, after: -1687188478
before: -1462842855, after: -1462842855

Quiero mostrar que el primer hilo realiza i++, el segundo hilo para ver el cambio de i, el primer hilo y el segundo hilo se ejecutan alternando mecanismos de espera y notificación, pero en realidad no funcionó de la manera que yo quería que así fuera.

Quiero saber por qué la palabra clave volátil no funciona

Albin Chang avatar Feb 16 '24 19:02 Albin Chang
Aceptado

Hay algunos problemas que puedo ver en su código.

Primero, ambos bucles son infinitos porque no son variables incrementales j. A continuación, el uso de notify/wait. El javadoc para el waitmétodo ( https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait ) menciona específicamente que "son posibles interrupciones y reactivaciones espurias, y este método siempre debe utilizarse en un bucle". El waitmétodo está diseñado para usarse con variables de condición ( https://cseweb.ucsd.edu/classes/sp17/cse120-a/applications/ln/lecture7.html ), por lo que es necesario que haya una condición y un bucle.

Además, hay un error lógico. Si el objetivo es ordenar la escritura y la lectura de modo que el antes y el después nunca sean iguales, es necesario que haya una waitllamada antes de la llamada a addy una notifyllamada después de realizar la lectura anterior.

A continuación, pongo una versión funcional de lo que creo que estás intentando hacer.

public class Main3 {
    private static volatile int i = 0;
    private static volatile boolean write = false;
    private static volatile boolean read = false;
    private static final Object o = new Object();

    private static void add()
    {
        i++;
    }

    public static void main(String[] args) throws InterruptedException {
        //The first thread performs the write operation
        Thread th1 = new Thread( () -> {
            for (int j = 0; j < 10000; j++) {
                synchronized (o)
                {
                    while (!write) {
                        try {
                            o.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    add();
                    write = false;
                    read = true;
                    o.notify();
                }
            }
        } );

        //The second thread performs the read operation
        Thread th2 = new Thread( () -> {
            for (int j = 0; j < 10000; j++) {
                int before;
                int after;
                synchronized (o)
                {
                    before = i;
                    
                    write = true;
                    o.notify();
                    
                    while (!read) {
                        try {
                            o.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    read = false;
                    after = i;
                }
                
                if( before == after )
                {
                    System.out.println( "before: " + before + ", after: " + after );
                }
            }
        } );


        th1.start();
        th2.start();

        th1.join();
        th2.join();

        System.out.println(i);
    }
}
Wil Gaboury avatar Feb 16 '2024 13:02 Wil Gaboury