La palabra clave java volátil no se comporta como se esperaba
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
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 wait
mé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 wait
mé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 wait
llamada antes de la llamada a add
y una notify
llamada 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);
}
}