简体   繁体   中英

Java volatile keyword in multithreading

I tried to reproduce the non-volatile variable behavior in Java multi-threading. Here I have non-volatile variable test in OccurrenceCounter.java class. In ThreadDemo.java class I have main method that spawns two threads. In thread1 it continuously check the value of non-volatile variable test in while loop. If I run below example in MyRunnableThread1.java the value of occurrenceCounter.isTest() is not taken from CPU cache.

OccurrenceCounter.java:

public class OccurrenceCounter {

    // non-volatile variable
    private   boolean test=true;

    public OccurrenceCounter(boolean test)
    {
        this.test=test;
    } 

    public boolean isTest() {
        return test;
    }

    public void setTest(boolean test) {
        this.test = test;
    }
}

MyRunnableThread1.java:

// Thread 1
public class MyRunnableThread1 implements Runnable {

    private String threadName;
    private OccurrenceCounter occurrenceCounter;

    public MyRunnableThread1(String threadName,OccurrenceCounter occurrenceCounter)
    {
        this.threadName=threadName; 
        this.occurrenceCounter=occurrenceCounter;
    }

    @Override
    public void run() {

        System.out.println("Thread "+threadName + " started");
        System.out.println("value of flag:"+occurrenceCounter.isTest());

        int i=0;
        // random processing code 
        while(i<1000000000L)
        {
           i++; 
           i++;
           i++;
        }
        // checking whether non-volatile variable is taken from cpu cache
        while(occurrenceCounter.isTest())
        {

        }
    System.out.println("Thread "+threadName + " finished");
    }
}

MyRunnableThread2.java:

// Thread 2
public class MyRunnableThread2 implements Runnable {

    private String threadName;
    private OccurrenceCounter occurrenceCounter;

    public MyRunnableThread2(String threadName,OccurrenceCounter occurrenceCounter)
    {
        this.threadName=threadName; 
        this.occurrenceCounter=occurrenceCounter;
    }

    @Override
    public   void run() {

        System.out.println("Thread "+threadName + " started");

        occurrenceCounter.setTest(false);
        System.out.println("Thread "+threadName + " finished");
    }
}

ThreadDemo.java:

public class ThreadDemo {

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

       System.out.println("main thread started");

       OccurrenceCounter occurrenceCounter =new OccurrenceCounter(true);

       MyRunnableThread1 myRunnableThread1=new MyRunnableThread1("Thread1", occurrenceCounter);
       MyRunnableThread2 myRunnableThread2=new MyRunnableThread2("Thread2", occurrenceCounter);

       Thread t1=new Thread(myRunnableThread1);
       Thread t2=new Thread(myRunnableThread2);

       t1.start();

       try
       {
           Thread.sleep(100);
       }
       catch(Exception e)
       {
           System.out.println("main thread sleep exception:"+e);
       }

       t2.start();

       System.out.println("main thread finished");
   }
}

Here in MyRunnableThread1.java in while loop the the condition occurrenceCounter.isTest() is not returned from CPU cache even though test variable in OcuurenceCounter class is non-volatile. But if I remove the first while loop:

while(i<1000000000L)
{
   i++; 
   i++;
   i++;
}

Then I can see that the condition occurrenceCounter.isTest() is always false even though it is update by thread2 and thread1 never terminates. So why this while loop:

while(i<1000000000L)
{
   i++; 
   i++;
   i++;
}

Impacting the behavior of non-volatile variable? Is this first while loop forcing anyway to read value from memory instead of from CPU cache in of thread1 ? I tried to get answer for this a lot. But I couldn't.

Please anyone help me to figure this out.

With volatile , JMM can gurantee the updated value will be available to other threads.

Without volatile or other synchronization, the updated value could or could not be available to other threads, it is uncertain.

In this case, the loop

while(i<1000000000L) {
    i++; 
    i++;
    i++;
}

can not gurantee memory visibility of variable test , it is just coincidence. Do not depend on it.

The volatile keyword makes sure that if one Thread makes some changes on that variable, then other Thread s that read it after the change, they will see it. If you use a plain, non- volatile variable, then other Threads might and might not see it. It depends on the internals of the JVM.

So, how to fix it? There are several ways:

  1. As you mentioned, you can make it volatile . That's probably the simplest way to do it:

     public class OccurrenceCounter { private volatile boolean test=true; // ... } 
  2. you can make the accessors synchornized . In this case you have to synchronize all the variable accesses:

     public class OccurrenceCounter { private boolean test=true; public OccurrenceCounter(boolean test) { this.test=test; } public synchronized boolean isTest() { return test; } public synchronized void setTest(boolean test) { this.test = test; } } 
  3. You can use an AtomicBoolean that handles all of this for you. The price you pay for this is that the API will be slightly more verbose. It's probably an overkill, unless you want to use the compareAndSet() or getAndSet() methods:

     private AtomicBoolean test = new AtomicBoolean(true); public OccurrenceCounter(boolean test) { this.test.set(test); } public boolean isTest() { return test.get(); } public void setTest(boolean test) { this.test.set(test); } 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM