Once a shared variable (a member variable of a class, or a static member variable of a class) is volatile, there are two levels of semantics:

  1. This ensures visibility when different threads operate on the variable, i.e. when one thread changes the value of a variable, the new value is immediately visible to other threads.
  2. Command reordering is disabled.

Let’s look at a piece of code:

If thread 1 executes first, thread 2 executes later: // thread 1 Boolean stop =false;
while(! stop){ //doSomething(); } // thread 2 stop =true;
Copy the code

In fact, does this code actually execute thread 1 and then thread 2? The answer is affirmative: no. Two threads are doing different things. There is no absolute priority, so there are two answers (stop=false for thread 1, stop=true for thread 2, unrelated).

######, however, is different with volatile:

First, using the volatile keyword forces the modified value to be written to main memory immediately.

Second, using volatile will invalidate the stop line in thread 1’s working memory when thread 2 changes it (or the L1 or L2 line on the CPU).

Third: thread 1 reads stop again from main memory because the cache line of stop is invalid in thread 1’s working memory.

So you can be sure that no matter how many threads run, the stop value is the same. This takes advantage of the thread visibility principle of volatile.

###2. Does volatile guarantee atomicity? The volatile keyword guarantees visibility, but does volatile guarantee atomicity on variables? Take a look at this code:

public class Test {
    public volatile int inc = 0;

    public void increase() {
        inc++;
    }

    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0; i<10; i++){ newThread(){
                public void run() {
                    for(int j=0; j<1000; j++) test.increase(); }; }.start(); }while(Thread.activecount ()>1) // Ensure that all previous threads have finished executing thread.yield (); System.out.println(test.inc); }}Copy the code

With the visibility of volatile described above, I think you should be able to quickly figure out the answer to this code: 10000. In fact, the printed number is always less than 10,000. This leads to the connection between volatile and atomic operations. Reason: Increment operations are not atomic, and volatile does not guarantee that any operation on a variable is atomic.

  • Increment operations are non-atomic and include reading the original value of a variable, incrementing by one, and writing to working memory. That is, the three children of the increment operation may be executed separately
  • If thread 1 gets a variable value from memory, and the increment is obstructed, thread 2 gets the same value and then increments, the two threads end up writing the same value.

##### How to modify it? There are three ways

  1. Add synchronized to the increment method
public class Test {
    public  int inc = 0;

    public synchronized void increase() {
        inc++;
    }

    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0; i<10; i++){ newThread(){
                public void run() {
                    for(int j=0; j<1000; j++) test.increase(); }; }.start(); }while(Thread.activecount ()>1) // Ensure that all previous threads have finished executing thread.yield (); System.out.println(test.inc); }}Copy the code
  1. Use the Lock
public class Test {
    public  int inc = 0;
    Lock lock = new ReentrantLock();

    public  void increase() {
        lock.lock();
        try {
            inc++;
        } finally{
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0; i<10; i++){ newThread(){
                public void run() {
                    for(int j=0; j<1000; j++) test.increase(); }; }.start(); }while(Thread.activecount ()>1) // Ensure that all previous threads have finished executing thread.yield (); System.out.println(test.inc); }}Copy the code
  1. Using AtomicInteger
public class Test {
    public  AtomicInteger inc = new AtomicInteger();

    public  void increase() {
        inc.getAndIncrement();
    }

    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0; i<10; i++){ newThread(){
                public void run() {
                    for(int j=0; j<1000; j++) test.increase(); }; }.start(); }while(Thread.activecount ()>1) // Ensure that all previous threads have finished executing thread.yield (); System.out.println(test.inc); }}Copy the code

In Java 1.5 Java. Util. Concurrent. Atomic package provides some atomic operation, namely the basic data types on the (1), since (1) operation, reduction and addition operation (plus a number), subtraction operation (subtract a number) for the encapsulation, ensure that these operations are atomic operations. Atomic operations use CAS to perform atomic operations (Compare And Swap). CAS is actually implemented using the CMPXCHG instruction provided by the processor, which is an atomic operation.

###3. Does volatile guarantee order? The volatile keyword mentioned earlier prevents instruction reordering, so volatile ensures some degree of order.

We’ve seen some of the uses stemming from the volatile keyword. Now we’ll look at how volatile actually ensures visibility and disallows instruction reordering.

The following excerpt is from Understanding the Java Virtual Machine:

“Looking at the assembly code generated with and without volatile, we found that volatile had an extra lock prefix.”

The LOCK prefix directive actually acts as a memory barrier (also known as a memory fence) that provides three functions:

  1. It ensures that instruction reordering does not place subsequent instructions in front of the memory barrier, nor does it place previous instructions behind the barrier; That is, by the time the memory barrier instruction is executed, all operations in front of it have been completed;

  2. It forces changes to the cache to be written to main memory immediately.

  3. If it is a write operation, it invalidates the corresponding cache line in the other CPU.