Understanding volatile

  But it is still a bit difficult to understand volatile, which is related with the Java memory model, so before you need to understand the concept of volatile understanding about the Java memory model, currently only preliminary introduction.

First, the operating system semantics

  When the computer running the program, each instruction is executed in the CPU during execution inevitably involve reading and writing data.

  We know that the data is stored program running in main memory, then there will be a problem, read and write data in main memory is not fast CPU to execute instructions, if any interaction is required to deal with the main memory will greatly affect the efficiency, so there is a CPU cache.

  CPU CPU cache for a unique, only with the thread running in the CPU.

1, a data consistency / memory invisible problem

  With Although the CPU cache efficiency to solve the problem, but it will bring a new problem: memory invisibility -> data consistency.

  Thread running process will copy the data to main memory to the cache internal thread, i.e. Working Memory . This time multiple threads access the same variable, in fact, access to its own internal cache, no longer dealing with the main memory only when the run ended after the data will be flushed to main memory.

  [ Cite a simple example]:

public class VariableTest {

    public static boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        ThreadA threadA = new ThreadA();
        ThreadB threadB = new ThreadB();

        new Thread(threadA, "threadA").start();
        Thread.sleep(1000l);//为了保证threadA比threadB先启动,sleep一下
        new Thread(threadB, "threadB").start();


    }

    static class ThreadA extends Thread {
        public void run() {
            while (true) {
                if (flag) {
                    System.out.println(Thread.currentThread().getName() + " : flag is " + flag);
                    break;
                }
            }

        }

    }

    static class ThreadB extends Thread {
        public void run() {
            flag = true;
            System.out.println(Thread.currentThread().getName() + " : flag is " + flag);
        }
    }
}

  The result: (cursor has been flashing)

  The reason is that the above examples of problems:

      (1) A thread the variable flag (to false) is loaded into its own internal buffer in the cache; modified variable flag after thread B, even if re-written in the main memory, but the thread does not re-A flag variable load from main memory, see or their cache variables in the flag. So read the thread A is less than the value of the update thread B, and has been an endless loop ...

      (2) addition to the reasons of the cache, instruction reordering when multiple threads of execution may also lead to memory is not visible due to the adjustment of the sequence of instructions, thread A reads a variable time thread B may not write to it , although the write operation is the code sequence in front.

2, solve cache coherency scheme

  1.  By way of the bus-lock LOCK #
  2.  Through cache coherency protocol

  But there is a problem Scenario 1 : it is to adopt a exclusive way to achieve, namely a bus-LOCK # lock, then only one CPU can run other CPU had blocked more efficiency is low.

  The second option : cache coherency protocol (MESI protocol) it ensures that each copy of the shared variable used in the cache is the same.

        The core idea is as follows : When a CPU when writing data, if found operating variable is a shared variable, it will notify the other CPU cache line informed of the variable is invalid, so other CPU at the time of reading the variables found invalid reloaded from main memory

                data.  

                           

Two, Java Memory Model

1, shared variables

     共享变量是指:可以同时被多个线程访问的变量,共享变量是被存放在堆里面,所有的方法内的临时变量都不是共享变量。

2、重排序

  重排序是指为了提高指令运行的性能,在编译时或者运行时对指令执行顺序进行调整的机制。重排序分为编译重排序和运行时重排序。

  编译重排序是指:编译器在编译源代码的时候就对代码执行顺序进行分析,在遵循as-if-serial的原则前提下对源码的执行顺序进行调整。

  运行时重排序:是指为了提高执行的运行速度,系统对机器的执行指令的执行顺序进行调整。

 【as-if-serial原则】是指在单线程环境下,无论怎么重排序,代码的执行结果都是确定的。

3、可见性

  内存的可见性是指线程之间的可见性,一个线程的修改状态对另外一个线程是可见的,用通俗的话说,就是假如一个线程A修改一个共享变量flag之后,则线程B去读取,一定能读取到最新修改的flag。

    (看上边示例)  

  Java提供了volatile来保证可见性。

  当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,当其他线程读取共享变量时,它会直接从主内存中读取。 

  当然,synchronize和锁都可以保证可见性。

4、原子性

  原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。就像数据库里面的事务一样,他们是一个团队,同生共死。

  在单线程环境下我们可以认为整个步骤都是原子性操作,但是在多线程环境下则不同,Java只保证了基本数据类型的变量和赋值操作才是原子性的。

  想在多线程环境下保证原子性,则可以通过锁、synchronized来确保。

// 一个很经典的例子就是银行账户转账问题:
      比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。试想一下,如果这2个操作不具备原子性,会造成什么样的后果。            
      假如从账户A减去1000元之后,操作突然中止(B此时并没有收到)。
      然后A又从B取出了500元,取出500元之后,再执行 往账户B加上1000元 的操作。这样就会导致账户A虽然减去了1000元,但是账户B没有收到这个转过来的1000元(少了500)。

      所以这2个操作必须要具备原子性(不可分割的最小操作单位)才能保证不出现一些意外的问题。  

三、volatile原理

  (1)volatile修饰的变量不允许线程内部cache缓存和重排序,可以保证内存的可见性和数据的一致性。

   (2)线程读取数据的时候直接读写内存,同时volatile不会对变量加锁,因此性能会比synchronized好。

  另外还有一个说法是使用volatile的变量依然会被读到线程内部cache中,只不过当B线程修改了flag后,会将flag写回主内存,同时会通过信号机制通知到A线程去同步内存中flag的值。

 volatile相对于synchronized稍微轻量些,在某些场合它可以替代synchronized,但是又不能完全取代synchronized,只有在某些场合才能够使用volatile。
使用它必须满足如下两个条件:
// 1、对变量的写操作不依赖当前值; // 2、该变量没有包含在具有其他变量的不变式中
 volatile经常用于两个两个场景:状态标记、double check(单例模式)

 

Guess you like

Origin www.cnblogs.com/timetellu/p/11618680.html