volidate的作用及其实现原理

volidate关键字在JDK中有大量应用,其主要作用有二个:1是保证变量在多线程之间的可见性;2是禁止指令重排序;但是volidate并不具备原子性。下面将分别介绍可见性、指令重排序、实现原理及为什么不能保证原子性。

可见性:可见性是指多线程情况下线程能够自动发现volatile变量的最新值。如果对Java内存模型比较了解的话会知道,每个线程都会被分配一个线程栈,如果对象是多线程间的共享资源时,当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的值load到线程中,建立一个变量副本,之后线程操作的都是副本变量,当修改完副本变量之后,会写回值到主内存。但由于线程栈是线程间相互隔离的,即多线程间不可见,如果有其他线程修改了这个变量,但还未写回主内存或者更新主内存后,其他线程读取的仍是自己线程栈的副本时,就会出现问题。而volidate则是用来保证可见性。即一个线程对共享变量的修改,能够及时被其他线程看到。

指令重排序:在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。虽然代码顺序是有先后顺序,但真正执行时却不一定按照代码顺序执行。这样在多线程下就可能存在问题。注意:只是可能出现问题,另外指令重排序在实际下发生情况比较少,由于Java、CPU和内存之间都有一套严格的指令重排序规则,具体可参照JSR和JVM相关资料。重排序分三种类型:

  1. 编译器优化的重排序:编译器在不改变单线程程序语义的前提下,重新安排语句的执行顺序。
  2. 指令级并行的重排序:现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  3. 内存系统的重排序:由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

原理:

 

第一张图没有加volatile,第二张图加上volatile,对比汇编指令可以发现加上volatile后在对变量进行add操作时,会加上lock,通过lock指令形成内存屏障(具体可查询Lock指令和JVM相关资料),而内存屏障有三个作用:1是阻止屏障两边的指令重排序;2是强制把写缓冲区/高速缓存中的脏数据等写回主内存;3是如果是写操作,它会导致其他CPU中对应的缓存行无效。而具体如何做到第3点,则与MESI缓存一致性协议有关,这个属于硬件级别,利用的CPU总线嗅探机制,感兴趣者可自行查阅。

不保证原子性:原子性是指这个操作是不可中断,要么全部执行成功要么全部执行失败,就算在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。volatile并不能保证原子操作,例如i++操作时,分为Load、Increment、Store、Memory Barriers四个步骤,即装载、新增、存储和内存屏障四个步骤,第四步则是保证jvm让最新的变量值在所有线程可见,但从Load、Increment、到Store是不安全的,中间如果其他的CPU线程修改值将会存在问题。

如何查看汇编指令:

  1. 将下载解压后的文件放入JDK所在目录下的jre/bin/server/下,下载地址:https://pan.baidu.com/s/1AmS2MCUygBihZxQmaU9vAw(提取码:3sri)
  2. 添加run configuration参数

-- 查看类全部指令:-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly

--查看指定方法:-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:-Inline -XX:CompileCommand=print,*className.methodName

     3. run 运行即可

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值