java keyword volatile not behaving as expected

96 views Asked by At

I experimented to verify the visibility of volatile

This is my test code:

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);


    }
}

Output:

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

I want to show that the first thread performs i++, the second thread to see the change of i, the first thread and the second thread are executed by alternating pass through wait and notify mechanisms, but it didn't actually work out the way I wanted it to.

I want to know why keyword volatile does not works

2

There are 2 answers

1
Wil Gaboury On BEST ANSWER

There are a few issues that I can see in your code.

First, both of the loops are infinite because they are not incrementing variable j. Next, the use of notify/wait. The javadoc for the wait method (https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait) specifically mentions that "interrupts and spurious wakeups are possible, and this method should always be used in a loop". The wait method is designed for use with condition variables (https://cseweb.ucsd.edu/classes/sp17/cse120-a/applications/ln/lecture7.html), so there needs to be both a condition and a loop.

Additionally, there is a logic error. If the goal is to have the writing and reading be ordered so that before and after are never equal, there needs to be a wait call before the the call to add, and a notify call after doing the before read.

Below, I've put a working version of what I think you are attempting to do.

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);
    }
}
1
Albin Chang On

spurious wakeups is the problem,it causes the second thread not to wait for the first thread to perform the write operation

I changed my code like this, it works

package org.example;

// 按两次 Shift 打开“随处搜索”对话框并输入 `show whitespaces`,
// 然后按 Enter 键。现在,您可以在代码中看到空格字符。
public class Main4 {

    private static volatile int i = 0;

    private static Object o = new Object();

    private static volatile boolean isWrite = false;

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

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

        //第一个线程执行写操作
        Thread th1 = new Thread( () -> {
            for (; ; ) {


                synchronized (o)
                {
                    add();
                    isWrite = true;
                    o.notify();


                }


            }
        } );

        //第二个线程执行读操作
        Thread th2 = new Thread( () -> {
            for (; ; ) {

                int before = i;
                synchronized (o)
                {
                    isWrite = false;
                    while (!isWrite) {
                        try {
                            o.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }

                int after = i;

                if( before == after )
                {
                    // 如果wait被虚假唤醒了,before和after就会相等
                    // this code should nerver be executed  
                    System.out.println( "before: " + before + ", after: " + after );
                }

            }
        } );


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

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

        System.out.println(i);


    }
}