Java并发锁之Volatile的原理与使用

简介

Volatile 是一种稍弱的同步机制(比synchronized要弱),用于修饰变量,这样可以确保将变量的更新操作(被一个线程更新)为其他线程所知。

Volatile的两个特性:1. 保证数据可见性;2. 禁止指令重排列。

原理

volatile一般在多核CPU中使用,并且线程是异步的。当某个线程对共享数据进行了写操作之后,由于在java内存模型中,每个线程都有自己独立的内存空间,也叫线程缓存(也叫CPU缓存)。而所有线程共享访问的是主存区域的变量、资源。所以当某个线程对某个资源进行了写操作之后,如果没有及时更新到主存中去,那么其他线程访问的这个资源则就是已过期的,无效的。
如下图所示,CPU1对Counter进行了写操作,把值从0变成了5,但是却没有“及时”把修改后的结果写回主存。此时对于CPU2而言,它所能看见的只有主存中的Counter(此时仍为0),所以它(CPU2)就认为Counter并没有改变,仍用的是Counter=0:

这里写图片描述

以上是两个CPU的数据不同步情况,更不用说目前普遍的4核8核CPU了。

当Counter用关键字volatile修饰后,volatile通过在底层加入内存屏障,防止指令重排序,来保证每个线程在对该数组进行操作时,总能看到其它线程对该volatile变量最后的写入:

这里写图片描述

Before-After保证

Before-After保证其实是可见性的补充。指的是当一个线程1对volatile变量修改之前(before)的所有可见变量集合S,在线程2读取更新的volatile变量之后(after),S对于线程2也是可见的了,示意图入下:

这里写图片描述

其实,在线程一对volatile变量修改后,当它往主存中写入新的更新值的时候,其它被线程一修改的变量(下面的nonVolatile)也会一并和volatile变量一起推到主存,而这些变量也将会随volatile变量被读取而被一并读取到CPU缓存。举例如下:

Thread 1:
    sharedObject.nonVolatile = 123;//一并被推送到主存
    sharedObject.counter     = sharedObject.counter + 1;

Thread 2:
    int counter     = sharedObject.counter;
    int nonVolatile = sharedObject.nonVolatile;//一并被从主存中读取

不足

1:非原子性

虽然volatile能保证线程之间的可见性,但是对于一种情况volatile就处理不了,就是如果两个线程都在各自的CPU缓存中修改了相同的变量,也就是有两个以及两个以上的线程修改时,那同时push到主存时到底变量值用谁的呢?
这里写图片描述

2:非原子性

上面是有2个线程同时修改导致一个变量两个地方不相同的问题,另外一个情况还是变量多次修改的问题,假设第一个线程修改了变量,但是还没有提交给主存,虽然有volatile但是在还没提交的那个很短的时间里,线程2也向主存要变量,这时还是会产生变量不一致的难题。甚至线程2还可能更改变量,那就出现了有一个变量三个地方不相同的情况:
这里写图片描述

3:作用域

volatile只对变量起作用,对于变量的读写操作并不会影响到线程级别的读写操作,对于关键代码块和方法就需要用synchronized等。

上面三种情况不再是volatile的能力范畴了,需要更高级的锁机制才能处理这些问题,比如synchronized, JUC等等。后续会继续介绍相关的锁机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小谢backup

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值