java并发:volatile关键字

内容学习自:https://www.cnblogs.com/dolphin0520/p/3920373.html

计算机缓存一致性

每核cpu在执行指令时,都会将数据从主存中读取,然后在自身的高速缓存中操作修改,最后刷新到主存中.

但有个问题,如果此时主存有个变量

i = 0;

两个cpu线程紧挨着先后执行下面的代码;

i = i + 1;

可能会出现这个情况:线程1和线程 2都读取了i= 0 到自身的高速缓存中.线程1执行+1操作,写入到内存.线程2也执行+1操作写入内存.最终i = 1;

这就是缓存一致性的问题,有两个解决方案:

1.总线锁机制.操作共享资源都加锁.好吧,这效率就甭说了.

2.缓存一致性协议,最出名的是Intel的MESI,意思是说,当cpu写完数据后,会通知其他cpu,去查看其高速缓存中是否有该变量的副本,如果有的话,请设置为无效,然后重新去主存中读取.

那么上面的情况就变成了.

线程1和线程 2都读取了i= 0 到自身的高速缓存中.线程1执行+1操作,写入到内存,通知线程2.线程2发现了有i=0的副本,然后作废,从内存中读取i=1,然后执行+1操作.

原子性/可见性/有序性

原子性:经典的例子就是转账,不能中断.

可见性:共享的变量某线程修改后,立即刷新主存,那么这个改变对其他线程来说,便是可见的.

有序性:处理器出于优化的机制会对代码指令重排,但并不影响结果.

比如

int a = 1 ;//指令1
int b = 2 ;//指令2
int c = a + b;//指令3

那么处理器真正执行的

可能是:指令1 ---指令2 ----指令3,

也可能是:指令2---指令1---指令3

但绝不会先执行指令3 ,因为指令3具有依赖性.

这对于单线程来说,没什么问题.可是对于多线程来说呢?

博主也不清楚.

JAVA内存模型

java有自己的一套内存模型,并定义了执行顺序,但是出于效率考虑,java并没有限制物理机的一些提升操作.也即是java也存在着缓存一致性和指令重排序问题.

java内存模型规定:所有变量存在于虚拟机内的主存(类似于机器主存),每条线程都有一个工作内存(类似于cpu高速缓存).变量的更新都必须在工作内存中执行,然后刷新到主存.不能直接在主存中更新.

JAVA如何保证原子性:

java只保证基本数据类型的读取和赋值是原子性的.

int a = 1;//赋值10给a
int b = a;//读取a,赋值a给b
a ++;//读取a,赋值a+1给a

以上三个只有第一行是原子性的.其他都不是.

当然synchronized和Lock是保证代码片的执行时原子性的.

JAVA如何保证可见性:

volatile可以保证可见性,被volatile修饰的共享变量修改后会立即刷新到主存.

当然synchronized和Lock算是只允许单线程操作,对单线程来说,讨论可见性是没有意义的.

JAVA如何保证有序性:

volatile也可以保证有序性.在后面说.

当然synchronized和Lock算是只允许单线程操作,对单线程来说,讨论有序性是没有意义的.因为不影响结果.

实质上,java内部就对执行顺序有个规则,叫做happens-before原则(先行发生原则):

  • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
  • 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
  • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
  • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
  • 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
  • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  • 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
  • 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

解读部分:

规则1:几乎是所有编程的规则.自上而下从左至右.但依然有可能指令重排.

规则2:解锁发生在另一个线程的开锁之前.

规则3:对一个变量的写操作先行发生于后面对这个变量的读操作.非常精简.这个在后面会说.

真volatile

volatile的作用:

1.保证了不同线程对这个变量进行操作时的可见性,一个线程修改后,其他线程都知道.

2.禁止指令重排.

第一条它保证了可见性.

第二条的意思:

当执行到volatile时,前面的代码必须都执行完了.volatile后面的代码必须都未执行.不管是否重排.

int a = 1;
int b = 2;
boolean flag = true;//volatile
int c = 3;
int d = 4;

这段代码.执行到flag时,c和d都没有执行.

而且指令重排时,a和b可以互换,c和d可以互换,但是无法越界.a和c无法互换.

volatile就是一道栅栏.它可以保证一定的有序性.

但是不能保证原子性.

为什么?

假如两条线程都去操作下面代码片:

a++;

那么a++本身是 读取和更新两个步骤.

还记得java执行规则的第三条吗?volatile可以保证读取时在更新之前,但不能保证读取是在读取之前.

比如线程1读取了a,此时在更新状态.那么线程2就不能读取,因为线程1还没有更新完到主存.

但是如果线程1读取了a,还没有更新,那么线程2就可以读取,

 

并发情况下,volatile比synchronized更有效率,但因不具备原子性,所以不能代替synchronized.个人觉得场景可以在/缓存/更新等地使用.记得wx-tools的获取access_token就使用了这个关键字.

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值