Volatile关键字

内存模型

CPU指令执行速度快,数据保存在主存中,即物理内存,为避免降低CPU执行速度,将数据从主存中copy到高速缓存中去,直接从高速缓存读取和写入数据,执行完毕后再将数据刷回到主存。

当多核,多线程的时候,对内存中同一个变量的调用,放入各自的高速缓存去执行,可能就会造成缓存不一致的问题。通常称这种被多个线程访问的变量为共享变量

一个变量在多个CPU中都存在缓存(一般在多线程编程时才会出现),那么就可能存在缓存不一致的问题。

为了解决缓存不一致性问题,通常来说有以下2种解决方法:

  • 通过在总线加LOCK#锁的方式
    对总线加锁,阻塞了其他CPU对其他部件的访问(如主存),从而使得只能有一个CPU能使用这个变量的内存。但会导致其他CPU无法访问内存,导致效率低下。
  • 通过缓存一致性协议
    它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为 无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

并发性概念

原子性

一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

可见性

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

这里写图片描述

有序性

程序执行的顺序按照代码的先后顺序执行。

指令重排序:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

★注:
1、处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行。
2、指令重排序不会影响单个线程的执行,但是会影响到多个线程并发执行的正确性。

要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。

内存模型

Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。

java中对于原子性、可见性、有序性的保证:

java保证原子性

Java内存模型只保证了基本读取和赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)是原子性操作。

x = 10;         //语句1
y = x;         //语句2

语句1是直接将数值10赋值给x,也就是说线程执行这个语句的会直接将数值10写入到工作内存中。
语句2实际上包含2个操作,它先要去读取x的值,再将x的值写入工作内存,虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作,但是合起来就不是原子性操作了

如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现

java保证可见性

对于可见性,Java提供了volatile关键字来保证可见性。

当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值

通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

java保证有序性

在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

volatile关键字来保证一定的“有序性,。另外可以通过synchronized和Lock来保证有序性,

java内存模型:happens-before原则,具备一定先天的有序性,无需任何其他手段就能保证有序性。

这里写图片描述

程序次序规则:在单线程中,对于不存在数据依赖性的指令会出现指令重排序,但是最终结果和代码执行顺序结果是正确的,因此在单线程中看起来是程序有序执行的,但是不适用多线程并发操作。

深入解析Volatile关键字

1、volatile保证了可见性

不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。禁止进行指令重排序

2、volatile不保证原子性

volatile没办法保证对变量的操作的原子性。

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++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }

        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

3、volatile一定程度保证有序性

volatile关键字禁止指令重排序有两层意思:
1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

volatile关键字使用场景

volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中

Java中使用volatile的几个场景:

1.状态标记量

volatile boolean inited = false;
//线程1:
context = loadContext();  
inited = true;            

//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

2.double check

/**
单例
*/
class Singleton{
    private volatile static Singleton instance = null;

    private Singleton() {}

    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章