Search code examples
javavolatile

java keyword volatile not behaving as expected


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


Solution

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