关键字之volatile

一、前言

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

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

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

二、三大性质解读其作用

1.可见性

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

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

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

(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,然后又开始新建。
在这里插入图片描述
参考:三大性质总结:原子性,有序性,可见性

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值