volatile和synchronized的区别

一、首先要了解Java的内存模型JMM

        此部分参考volatile和synchronized到底啥区别?

    ①为了解决内存和CPU之间速度的差异,Java 添加了多个缓存:

        如图,在cpu和主内存之间,添加了两级缓存

    ②对应到具体线程中:

        每个线程都有自己的L1缓存,然后共用一个L2缓存,最后才是主内存

        共享变量将在L1、L2和主内存中都存在,这样当L1中存在该变量就不必去主内存寻找,补上了内存速度慢的短板。

    ③线程读/写共享变量时的步骤:

        a. 从主内存复制共享变量到自己的工作内存

        b. 在工作内存中对变量进行处理

        c. 处理完后,将变量值更新回主内存

二、共享变量内存不可见问题

    根据上面的缓存模型,可以发现一些共享变量的问题。

例如:

    在主内存中有变量 X=0

    线程1和线程2先后访问该变量

    线程1先访问:(修改X为1)

        a. L1 和 L2 中都没有发现变量 X,直到在主内存中找到

        b 拷贝变量 X 到 L1 和 L2 中

        c. 在 L1 中将 X 的值修改为1,并逐层写回到主内存中

    在线程1访问之后,线程1的L1、共享L2、主内存中X的值被修改为1

    线程2访问:(修改X为2)

        a. L1 中没有发现变量 X

        b. L2 中发现了变量X

        c. 从L2中拷贝变量到L1中

        d. 在L1中将X 的值修改为2,并逐层写回到主内存中

    在线程2访问之后,线程2的L1、共享L2、主内存中的X都被修改为2

    当线程1再次访问X:

        发现自己 L1 中 X=1, L2 中 X=2 ,二者值不一样。

        此刻,如果线程 1 再次将 x=1回写,就会覆盖线程2的 x=2 的结果,

        同样的共享变量,线程拿到的结果却不一样(线程1眼中x=1;线程2眼中x=2),这就是共享变量内存不可见的问题。

    即各个线程的工作内存不可见:

        A线程先读取共享变量X, B线程修改了共享变量a后为X

        推送给主内存并改写, 主内存不会推送给 A线程,

        A和 B的变量会不同步

    

    简单点来说就是不再参考 L1 和 L2 中共享变量的值,而是直接访问主内存

三、synchronized和volatile

1.volatile

    线程在【读取】共享变量时,会先清空本地内存变量值,再从主内存获取最新值

        因为会先清空本地内存变量值,再直接从主内存获取最新值,跳过了缓存,保证了只要使用volatile的变量,就会先从主内存同步

    线程在【写入】共享变量时,不会把值缓存在寄存器或其他地方(L1或L2),而是会把值刷新回主内存

        这样保证被volatile修饰的变量的值只要修改就会直接更新到主内存,不会使用缓存

    这样就解决了共享变量内存不可见问题

2.synchronized

    【进入】synchronized 块的内存语义是把在 synchronized 块内使用的变量从线程的工作内存中清除,从主内存中读取

    【退出】synchronized 块的内存语义事把在 synchronized 块内对共享变量的修改刷新到主内存中

3.区别

    这样看,二者都解决了内存可见性问题,但是二者有什么区别?

看例子:

    类中有个变量 count,有两个线程对其进行自增操作10000次,

    因为未加上述两种修饰符,如果多线程修改value,就会内存不可读导致最终结果小于20000

因为synchronized关键字不能修饰变量,因此将自增10000次封装成一个方法

如下图:

对于上图:

1.给count添加volatile关键字

    这样虽然解决了内存可见性问题,但是count++不属于原子操作,这样最终结果会小于20000

    因为volatile关键字不能保证原子性

2.给方法添加synchronized

    synchronized 是独占锁/排他锁(就是有你没我的意思)

    同时只能有一个线程调用 add10KCount 方法,其他调用线程会被阻塞。

    所以三行 CPU 指令都是同一个线程执行完之后别的线程才能继续执行,这就是通常说说的原子性 (线程执行多条指令不被中断)

    因此最终结果一定为20000.

四、什么时候使用volatile

    如果写入变量值不依赖变量当前值,那么就可以用 volatile

    synchronized 是排他的,线程排队就要有切换,要完成切换,还得记准线程上一次的操作,很累CPU大脑。

    这就是通常说的上下文切换会带来很大开销

    volatile 就不一样了,它是非阻塞的方式,所以在解决共享变量可见性问题的时候,volatile 就是 synchronized 的弱同步体现了

例如:

    一个加油站,多个加油机。

    这些加油机遵守加油站的定价标准。

    

不使用volatile:

    1.加油站定价为10元/L

    2.加油机1和2第一次加油时从主内存读取价格10元/L放入自己的本地内存L1和L2中

    3.之后每次都从本地内存中读取;

    4.此时,加油站改变定价为12元/L

    5.因为加油机1和2本地内存都有定价10元/L,不会去主内存读取最新的价格,造成了一些问题。

分析:

    上述例子可以发现,在加油机1和2中(也就是线程处理时,只读取价格的值,不修改价格的值或者说修改价格的值不依赖与价格原本的值)

    此时可以使用volatile关键字修饰价格,解决可见行的问题。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值