JAVA并发编程之volatile

前言

今天在学习单例模式的时候发现一种写法使用了volatile,对于它我也只是听说过它的名字,至于怎么使用,它有什么内涵,我确实是不甚了解。经过查阅资料学习,我有了一些自己的见解。

基本概念

介绍一下JAVA内存模型(JMM)中的原子性、可见性、有序性

  • 原子性

    原子的本意是“不能被进一步分割的最小粒子”。原子性是指一个操作或者多个操作是不可中断的,要么全部执行成功要么全部执行失败。

    现实中就存在这样的例子,比如转账问题:

    比如小红向小明的账户转账1000元,这个情景包含两个操作,小红的账户减去1000元,小明的账户加上1000元。

    如果这两个操作不具备原子性,试想转账时当小红账户减去1000之后,在给小明账户加1000时发生错误,操作失败,小明的账户并没有加上这1000,结果小红的1000元就这样凭空消失了。

    这样显然是不行的,我们所需要的是要么转账的两个操作全部成功,要么两个操作全部失败。这就是原子操作。

    在java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可中断的,要么执行,要么不执行。虽然看上去很简单,但具体却不是那么容易,看下面的例子:

    int a = 10; //1
    a++; //2
    int b=a; //3
    a = a+1; //4
    

    上面的四个语句中只有第一个语句是原子操作

    语句2实际上包含三个操作,读取a的值,进行加1操作,写入新的值

    语句3实际上包含两个操作,读取a的值,将a的值赋值给b

    语句4实际上包含三个操作,读取a的值,进行加一操作,写入新的值

    所以,只有简单的读取、赋值才是原子操作

  • 可见性

    可见性是指当一个线程修改可共享变量之后,其他线程能够立即得知这个修改。

    在java中,提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,如果一个线程更改了变量值,它会将本地内存中的数据写会主内存,并通知其它线程将他们的本地内存置为无效,接下来需要从主内存中读取共享变量。

    另外,通过synchronized和Lock也能保证可见性,他们能保证同一时刻只有一个线程获取锁然后执行同步代码,获取锁时会从主内存中获取共享变量的最新值,释放锁的时候会将共享变量同步到主内存中。

  • 有序性

    在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序(在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快),重排序过程不会影响单线程程序的执行,却会影响到多线程并发执行的正确性。

    重排序例子:

    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指向刚分配的内存地址。但由于重排序的问题,则可能存在以下的执行顺序:1-3-2

    如果2和3进行了重排序,线程B进行判断if(instance==null)就会为false,返回未初始化的对象,程序出错。

    但如果加入volatile就会不同,因为它会禁止2和3操作重排序。volatile包含禁止指令重排序的语义。

    java内存模型存在一些默认的有序性,这个通常称为happpens-before原则,如果操作不满足这些原则,那么他们就不能保证他们的有序性,可以随意的重排序。那么下面就来具体介绍happens-before原则:

    • 程序顺序规则:一个线程中的每个操作,happens-before与该线程中的任意后续操作
    • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁(解锁操作先行发生与加锁操作)
    • volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读
    • 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C
解析volatile关键字
定义

Java编程语言允许线程访问共享变量,为确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加的方便。如果一个字段被声明成volatile,Java线程内存模型确保所有的线程看到这个变量的值是一致的。

实现原理

有volatile变量修饰的共享变量进行写操作时,会比普通的写操作多出一行汇编代码,核心是Lock前缀指令,它在多核处理器下会引发两件事:

1)将当前处理器缓存行的数据写会到系统内存

2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效

轻量

在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。

特性

当一个变量定义为volatile之后,将具备两种特性:

1)保证此变量对所有线程的可见性,即一个线程修改了变量的值,这个新值对其他线程也是立刻可见的

2)禁止指令重排序优化。有volatile修饰的变量,通过添加内存屏障来限制重排序

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值