欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

关键字之volatile

程序员文章站 2022-04-11 18:44:57
...

一、前言

昨晚心血来潮了解了一下volatile关键字,了解之后,内心迫不及待想要记录自己的所见所感。
说到这个关键字,会牵扯到内存之间的 交互,主内存与工作内存,以及并发编程中牵扯线程安全的时候的三大性质:原子性,有序性,可见性。

volatile	:[ˈvɒlətaɪl],易变的,不稳定的

字面理解:加了此关键字的变量会被多个线程所改变,初步感知会跟static很相反。
下面会从那个透过三大性质来解读此关键字的作用。

二、三大性质解读其作用

1.可见性

当一个变量被附加上volatile关键字的时候,若一个线程改变它的值,那么其他线程可以立即访问到它改变之后的值并且也可以改变他。

(1)Java内存模型:主内存与工作内存

Java的内存模型规定了所有变量都会放置在主内存,每个线程都有自己的工作内存,线程不能直接访问主内存,而是通过访问自己工作内存,取出变量内容。
当线程需要取出一个数据的时候,会有一条指令发到工作内存说需要这个变量,cpu接收到指令之后就会去主内存访问取出数据放在线程的工作内存。
当线程要修改数据的时候,会先修改工作内存的数据内容,然后执行Sava指令,存到主内存。
关键字之volatile

(2)缓存不一致性问题解决

看到上面访问数据和写数据,那么大家可能会有这么一个疑问,比如主内存的"i=0",线程1访问到i=0,并令i=i+1,即i=1,写到了工作内存,但是此时线程2访问主内存得到i=0,并使i=i+1,接着线程1工作内存的i=1,写到了主内存i=1,最后线程2将工作内存里的i写到主内存,所以最终结果的i=1。

int i=0;
线程1
i=i+1;

线程2
i=i+1;

i=1,这显然是不对的,所以这就是缓存不一致的问题了,一般来说,会有两种方法去处理这个问题。

i.lock

同理看到上面的代码,当线程1在执行读取更改i值的时候,在总线上加上LOCK#锁,然后线程2就无法访问主内存,直到更改完成就解开锁,但是这个方法,就会引起被锁在外面的cpu一直等待着,这使得效率极大地降低

ii.缓存一致协议

Intel 的MESI协议,当访问工作内存的共享变量的时候,就告诉其他的cpu的将该变量的缓存行置为无效状态,直到写完数据给主内存,当其他的cpu访问到缓存行无效的时候,就会重新读取内存的共享变量。

2.原子性

尽管上面的缓存一致协议看起来已经很完美了,但是,我们可以看到下面的测试样例。

public class VolatileExample {
    private static volatile int counter = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Runnable() {
          
                public void run() {
                    for (int i = 0; i < 10000; i++)
                        counter++;
                }
            });
            thread.start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(counter);
    }
}

我们运行这段代码的时候,并不能得到我们预想中的答案,问题就是,volatile关键字并不能保证原子性,counter++并不是原子操作,原子操作有八种(load,read,lock,unlock,assign,store),当线程1读取counter值到工作内存的时候,还没来得及改变,其他线程就对他进行了增加操作,那么等线程1增加之后的值重新写入到主内存,这个值就会跟只相当于增加了一次,所有结果肯定不是我们想的那样。
如果说得更加具体一点就是:对a,b的访问:read a,read b,load b,load a。
**规则:**若是需要volatile保证原子性,那么就需要满足:
1.运算结果并不会依赖变量的当前值,或者能够确保只有一个线程对变量进行修改;
2.变量不需要与其他状态共同参与不变约束。

3.有序性

volatile关键字能屏蔽指令重排序
如下面代码

public class Singleton {
    private Singleton() { }
    private volatile static Singleton instance;
    public Singleton getInstance(){
        if(instance==null){
            synchronized (Singleton.class){
                if(instance==null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

instance = new Singleton();,这行代码需要三步执行:(1)先分配对象内存空间;(2)初始化对象;(3)设置instance指向刚刚分配好的地址。
但是若是没有添加volatile关键字,线程1就很可能执行,1->3->2,当线程2访问instance是否为null的时候,就直接null,然后又开始新建。
关键字之volatile
参考:三大性质总结:原子性,有序性,可见性