简体   繁体   中英

Volatile keyword usage in Java

I am unable to understand that if my variable is both volatile and static then why threads are not reflecting the common shared value in the output Output of last few lines is : Thread is running 4998Thread-0 Thread is running 4999Thread-0 Thread is running 4899Thread-1 Thread is running

public class Test implements Runnable{
  volatile static int  i=0;
    @Override
    public void run() {
        for(;i<5000;i++)
        {   try {
            Thread t = Thread.currentThread();
      String name = t.getName();
      //  Thread.sleep(10);
                System.out.println(i+name);
            } catch (Exception ex) {
               ex.printStackTrace();
            }
        System.out.println("Thread is running");
    }}
    public static void main(String[] args) {
    Test t=new Test();
    Thread t1=new Thread(t);
    Thread t2=new Thread(t);
    t1.start();
      // t.run();
       t2.start();
    }
    }

You can't use a compound (multi step) operation like i++ on a volatile variable.

You can have both threads retrieve the value, increase it, and write it back resulting in one lost increment (as you see in your example).

Volatile ensures that changes to the variable are visible to other threads. But it does not ensure synchronization .

From one execution of your program I get this output :

Thread is running
3474Thread-1
Thread is running
3475Thread-0
Thread is running
3477Thread-0
(.... many lines where i goes from 3478 to 4998, with Thread-0 always being the one running...)
Thread is running
4999Thread-0
Thread is running
3476Thread-1
Thread is running

This happens because threads get slices of processor time to be run and their execution can be paused and resumed at any point. Here Thread-1 is executing line "System.out.println(i+name);" with i having a value of 3476. i+name is evaluated to "3476Thread-1" but just then the Thread-1 execution stops and instead Thread-0 gets its time slice. Thread-0 executes till finalization. And then Thread-1 gets again to execute. We have left it after i+name had been evaluated to "3476Thread-1" and before the call to println. The call is now completed and printed, hence you see "3476Thread-1" at then end. i has been increased to 5000 by Thread-0 but that does not change the result of the evaluation of i+name which was done before all those increases.

The problem is that i++ and i+name are different instructions and thread execution can be paused and resumed between them. To ensure that you get a secuential output you need to ensure than there is no interruption between i++ and i+name. That is, you need to make that set of instructions atomic.

public class Test implements Runnable{
    static Object lock = new Object();

  volatile static int  i=0;
    @Override
    public void run() {
        for(;;)
        {
            try {
                Thread t = Thread.currentThread();
                String name = t.getName();
                synchronized( lock )
                {
                    if ( i>=5000 )
                        break;
                    i++;
                    System.out.println(i+name);
                }
                //  Thread.sleep(10);
            } catch (Exception ex) {
               ex.printStackTrace();
            System.out.println("Thread is running");
            }
    }   }
    public static void main(String[] args) {
    Test t=new Test();
    Thread t1=new Thread(t);
    Thread t2=new Thread(t);
    t1.start();
      // t.run();
       t2.start();
    }
}

In that program, if Thread-1 gets paused between i++ and i+name it will be inside the a critical section controlled by synchronized(lock). When Thread-0 gets to execute it will reach the synchronized(lock) instruction and will have to stop executing until Thread-1 resumes and gets out of that block. Because the JLS ensures that :

The synchronized statement (§14.19) computes a reference to an object; it then attempts to perform a lock action on that object's monitor and does not proceed further until the lock action has successfully completed. After the lock action has been performed, the body of the synchronized statement is executed. If execution of the body is ever completed, either normally or abruptly, an unlock action is automatically performed on that same monitor.

Here's a code snippet I learned usage of volatile keyword from:

import java.util.concurrent.TimeUnit;

public class VolatileDemo {
    private volatile static boolean stop; //remove volatile keyword to see the difference
    private static int i = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread otherThread = new Thread(new Runnable() {
            public void run() {
                while (!stop)
                    i++;
            }
        });
        otherThread.start();
        TimeUnit.SECONDS.sleep(1);
        stop = true;// main thread stops here and should stop otherThread as well
    }
}

If you want to observe what volatile does, try to remove it and then follow the execution, this should be obvious after you run those two versions, but basically keyword here prevents java compiler from assuming that stop condition never changes, it will be read every time the condition gets evaluated. Neat, isn't it?

By looking at your code the problem isn't with usage of volatile keyword, the problem is that the expression i++ is not atomic. It actually is 3 step operation:

1) fetch value of i;
2) increment i;
3) save new value of i

When multi-threading comes into play, these might or might not be mixed with other thread's instructions. So the execution might look like that as well:

1) T1: fetch i;
2) T2: fetch i;
3) T1: increment i;
4) T2: increment i;
5) T1: save i;
6) T2: save i;

If i was 0 you thought you will get 2 as the output, and here's 1 instead.

Go with synchronization, which is pretty simple when not overused and well thought.

Suggested read on synchronization

If two threads are both reading and writing to a shared variable, then using the volatile keyword for that is not enough. You need to use synchronization in that case to guarantee that the reading and writing of the variable is atomic.

Therefore you need to use synchronization when you modify the i value.

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