Volatile keyword parsing

Posted by rotwyla98 on Sat, 04 Apr 2020 19:26:39 +0200

volatile is a lightweight synchronization mechanism provided by Java virtual machine (synchronized in beggar version)

  1. Ensure visibility
  2. No guarantee of atomicity
  3. Prohibit command rearrangement

visibility

When multiple threads access the same variable, if one thread modifies the value of the variable, other threads can immediately see the modified value

Verify visibility demo:

import java.util.concurrent.TimeUnit;

class MyData {
    volatile int number = 0;
    public void addTo60() {
        number = 60;
    }
}
public class VolatileDemo {
    public static void main() {
        MyData myData = new MyData();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t come in");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.addTo60();
            System.out.println(Thread.currentThread().getName() + "\t updated number: " + myData.number);
        }, "AAA").start();
        while (myData.number == 0) {}
        System.out.println(Thread.currentThread().getName() + "\t mission is over");
    }
}

Result:

AAA  come in
main     mission is over
AAA  updated number: 60

No guarantee of atomicity

Atomicity: all operations in a program are non interruptible. Either all operations succeed or all operations fail

No guarantee of atomicity is the embodiment of the lightweight of volatile. When multiple threads operate on variables decorated by volatile, it is easy to write over (i + +)

Verification does not guarantee atomic demo:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class MyData {
    volatile int number = 0;
    public void addPlusPlus() {
        number++;
    }
}
public class VolatileDemo {
    public static void main(String[] args) {
        MyData myData = new MyData();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    myData.addPlusPlus();
                }
            }, String.valueOf(i)).start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);
    }
}

Result:

main    finally number value: 19109

Solving the problem of non guaranteed atomicity: Atomic

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class MyData {
    volatile int number = 0;
    public void addPlusPlus() {
        number++;
    }

    AtomicInteger atomicInteger = new AtomicInteger();
    public void addAtmic() {
        atomicInteger.getAndIncrement();
    }
}
public class VolatileDemo {
    public static void main(String[] args) {
        MyData myData = new MyData();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    myData.addAtmic();
                }
            }, String.valueOf(i)).start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);
        System.out.println(Thread.currentThread().getName() + "\t AtomicInteger type,finally number  value: "
                + myData.atomicInteger);
    }
}

Result:

main     finally number value: 19746
main     AtomicInteger type,finally number  value: 20000

Prohibit command rearrangement

Instruction rearrangement: in order to improve the running efficiency of the program, the compiler may rearrange the input instructions, that is, the execution sequence of each statement in the program is not necessarily the same as that in the code. (but it will ensure that the final execution result of single threaded program is consistent with that of code sequential execution, and it ignores the dependency of data.)

Source code - > compiler optimization rearrangement - > instruction parallel rearrangement - > memory system rearrangement - > final execution instruction

volatile can realize the underlying principle of prohibiting instruction rearrangement:

  • Memory Barrier: it is a CPU instruction. Since both the compiler and the CPU can execute instruction reordering, if a Memory Barrier is inserted between instructions, the compiler and the CPU will be informed that no instruction can be reordered with the Memory Barrier instruction, that is, by inserting a Memory Barrier instruction, the reordering of instructions before and after the Memory Barrier can be prohibited
    optimization
  • Another function of the memory barrier is to force the cache data of various CPUs to be refreshed, so that any thread on the CPU can read the latest version of these data. The above two points just correspond to the characteristics of the volatile keyword, which forbids instruction reordering and memory visibility
  • When writing volatile variable, a store barrier instruction will be added after the write operation to refresh the shared variable copy in working memory back to main memory; when reading volatile variable, a load barrier instruction will be added before the read operation to read the shared variable from main memory

Application scenario:

  • Using volatile in DCL singleton mode in high concurrency environment

    public class SingletonDemo {
        private static volatile SingletonDemo instance = null;
        private SingletonDemo() {
            System.out.println(Thread.currentThread().getName() + "I'm the construction method SingletonDemo()");
        }
        public static SingletonDemo getInstance() {
            if (instance == null) {
                synchronized (SingletonDemo.class) {
                    if (instance == null) {
                        instance = new SingletonDemo();
                    }
                }
            }
            return instance;
        }
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    SingletonDemo.getInstance();
                }, String.valueOf(i)).start();
            }
        }
    }
    
  • AtomicXxx class under the JUC package: there is a member variable value in the atomic class AtomicXxx, which is declared as volatile, ensuring that
    The memory visibility of AtomicXxx class is guaranteed by CAS algorithm & unsafe class. Combining these two points, AtomicXxx class can replace synchronized keyword.

    public class AtomicInteger extends Number implements java.io.Serializable {
        // ...
        private volatile int value;
        // ...
    }
    

Topics: Java