【并发编程】简单认识volatile

并发编程是提高程序性能的有效手段,它是十分难掌握的一项技能,要求开发人员逻辑清晰、思维缜密也是拉开技术人员距离的重要标准,所以了解和学习并发编程是重要且有必要的。
☆☆☆编程之路就像通天塔打怪升级之路,一层一层向上爬。并发编程学习之路,任重而道远。☆☆☆
声明:谈到java并发编程,那么volatile是绕不开的话题,本文简单记录一下个人对volatile的简单认识,由于本人目前技术水平有限,如有不正确的地方轻喷,还请大佬们多多指教。

一.并发编程的三个基本概念

1.原子性
即一个或多个操作要么全部执行完且不被打断,或者都不执行。
2.可见性
多线程情形下,当一个线程对共享变量进行了修改,其他线程也要即时看到修改的值。
3.有序性
程序的执行顺序按照代码的先后顺序执行。

二.共享变量的可见性问题

JMM(java内存模型)规定了线程与主内存之间的抽象关系:线程使用的共享变量存储在内存中,每个线程有自己的本地内存(或称工作内存或缓存)【这个本地内存是一个抽象概念】,每个线程的本地内存中(其实就是缓存)存放该共享变量在主内存中的副本,线程的操作都是在工作内存中进行,而不是直接去读取主内存。
普通共享变量为什么不具备可见性?
普通共享变量即没有被volatile修饰的普通变量。 假设当前有两个线程:线程A和线程B,共享变量V初始值为1。双线程情况下一个可能的执行步骤:
step1:此时线程A执行V=V+1将V修改为2,此时线程A本地内存中V=2,然后JMM将线程A本地内存的共享变量刷新到主内存中,此时主内存中V=2。
step2:线程B此时执行读取V的操作,一开始线程B本地内存(缓存)中没有共享变量V的值,所以线程B去主内存中读取到共享变量V的值为2,并将该值存放到其本地内存(缓存)中。 到此为止,一切都是正常…
step3:此时线程A继续执行V=V+1操作,V的值变为3,此时JMM将共享变量更新到线程A本地内存并将其刷新到主内存中,主内存中V的值变为3。

此时问题来了!!!

step4:线程B再次执行读取V的操作,首先线程B去它的本地内存(缓存)中去查看是否有V,由于第三步对共享变量V的更新操作并没有刷新线程B,即线程B本地内存中V的值依然为2,此时线程B就读取到了V的旧值2。

图示:

在这里插入图片描述

那么如何保证共享变量的可见性呢?对变量的操作进行加锁,java提供了synchronized、lock或者volatile可以帮助解决共享变量在多线程中不可见性的问题,如果是单一变量操作,用volatile是最轻量级,因为它不会导致线程的上下文切换,那为什么被volatile修饰的共享变量就可以实现共享变量的可见性呢?紧接着看下面对volatile读/写的内存语义的简单概括。

三.锁的释放和获取的内存语义

锁获取的内存语义:获取到锁的那个线程的本地内存置为无效,从而使临界区代码从主内存中读取共享变量。
锁释放的内存语义:释放锁的那个线程将其本地内存中的共享变量刷新到主内存中。

四.volatile读/写的内存语义

volatile写内存语义:当对volatile变量执行写操作时,执行此操作的线程会将其本地内存里的共享变量刷新到主内存。
紧接着是volatile变量读的内存语义:当线程读volatile变量时,会将该线程本地内存的该变量值置为无效,然后从主内存中去读取该volatile变量,并将读到的变量刷新到自己的本地内存。

读到这里后再回头结合普通共享变量在多线程下的操作情况,想必也应该理解volatile变量为什么可以实现共享变量的可见性了吧?

​ 与读取普通共享变量不同的是,如果是读取普通共享变量,那如果线程的本地内存命中该变量,则直接从本地内存中读取,但是通过之前的图示也能看出来,这样无法保证本地内存的值一定是最新的。

​ 再结合volatile读/写的内存语义,读取读取volatile变量时都直接是从主内存中去读取后刷新到本地内存中,这就保证了每次读取的值都是最新值,到此为止,也就回答了为什么volatile修饰的变量是可见的。

五.volatile的特性

1.可见性

volatile的可见性在上面已经解释过。

2.禁止指令重排

所谓指令重排,是编译器和处理器为了提高程序运行效率、优化程序性能而对程序指令序列进行重排序,重排序也是有一定规则的,不是所有指令都可以重排序,重排序不能影响程序的执行结果。

如果变量被volatile修饰,那么当代码编译成指令序列后,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序,volatile禁止指令重排序的规则:当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行。

六.volatile可以保证可见性和有序性,那它能保证原子性吗?

​ 举个例子:如果变量i被volatile修饰,其在多线程下并发执行i++自增操作,但自增操作其实不是原子的,它可以分为三个原子操作:读取i(step1)、更新i(step2)、刷新到主存(step3)。 这时假设两个线程:线程A和线程B并发执行自增操作,初始i=0, 线程A首先执行了step1,读取i=1;此时线程B也指向了step1,读取i=1,这个时候线程A和B再各自执行step2,它们都同时将i的值更新为2并更新到主存及各自各自本地内存中, 那么这样的话就出现了漏算的情况,因为按理来说变量i执行两次自增的结果应该是3。

​ 通过前面的内容,可以简单总结出volatile能够禁止指令重排和cpu缓存(也就是前面所提到的本地内存),但是volatile无法阻止cpu中断,也就是说cpu无法中断,这也就避免不了上面例子中自增的三步操作被多线程并发的去执行,也就是说volatile不能保证原子性。如果想保证原子性,可以采用加锁的方法去实现。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值