浅析volatile原理及其使用

前言

经常在网上看一些大牛们的博客,从中收获到一些东西的同时会产生一种崇拜感,从而萌发了自己写写博客的念头.然而已经有这个念头很久,却始终不敢下手开始写.今天算是迈出了人生的一大步_!


volatile的定义及其实现

定义:如果一个字段被声明成volatile,那么java线程内存模型将确保所有线程看到的这个变量的值都是一致的.

从它的定义当中咱们也可以了解到volatile具有可见性的特性.但它具体是如何保证其可见性的呢?

先看一段JIT编译器生成的汇编指令

//Java代码如下
instance = new Singleton(); //这里instance是volatile变量
//反汇编后
0x01a3de1d: movb $0x0,0x1104800(%esi);
0x01a3de24: lock add1 $0x0,(%esp); 

有volatile修饰的变量在进行写操作时会出现第二行反汇编代码,重点在lock这个指令.它有两个目的:

1.立即回写当前处理器缓存行的值到内存.
2.其他所有cpu缓存了该地址的数据将会失效.

这里大家也许会有疑问,有没有可能存在多个cpu一起回写数据?

答案是不会的.虽然cpu鼓励多个处理器可以有竞争,但是总线会对竞争做出裁决,只会有一个cpu获取优先权.其他处理器会被总线禁止,处于阻塞状态.如下图:

对于第二点,其他cpu缓存该地址的数据失效后想要再次使用的话就必须得从主内存中重新读取,这样就能保证再次执行计算时所获取的值是最新的,也可以认为所有CPU的缓存是一致的,这也就证明了volatile修饰的字段是可见的.


可见性不代表在并发下是安全的

这里咱们先引进一段代码:

/**
 * volatile 变量自增运算
 *
 * @author mars
 */
public class VolatileTest {public static volatile int count = 0;public static void increase() {count++;}private static final int THREAD_COUNTS = 20;public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(THREAD_COUNTS);Thread[] threads = new Thread[THREAD_COUNTS];for (int j = 0; j < THREAD_COUNTS; j++) {threads[j] = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {increase();}latch.countDown();}});threads[j].start();}//等待所有的线程执行结束latch.await();System.out.println(count);}
} 

这段代码供发起了20个线程,对count变量进行了10000次自增操作,如果volatile修饰的字段在并发下是安全的话,讲道理最终结果都会是200000,但经过测试发现,每次的输出结果都会不一样.但具体是什么原因造成的?

其实最主要的问题是出在increase()这个自增方法上,这个操作不是一个原子操作,也就是不是一步就能操作完成的,其中会经历count值入栈,add,出栈,到操作线程缓存,最终到内存等等一系列步骤.当A线程其执行这些指令时,B线程正好将数据同步到了主内存中,此时A线程栈顶的数据就会变成过期数据,然后A线程就会将较小的值同步到主内存中.


如何正确的运用volatile

要想运用好volatile修饰符,需要保证运用场景符合下述规则:

1.运算结果不依赖变量的当前值.
2.该变量不需要和其他变量共同参与约束.

例如使用volatile变量来控制并发就很合适:

volatile boolean shutdownWork;public void shutdowm(){shutdownWork = true;}public void doWork(){while (!shutdownWork){//execute task}} 

上面这段代码运行结果并无需依赖shutdownWork的值,但是只要shutdownWork的值一旦经过改变,便会立即被其他所有线程所感知,然后停止执行任务.


小知识点

在多处理器下,为了保证各个处理器的缓存是一致的,处理器会使用嗅探技术来保证它的内部缓存,系统内存和其他处理器的缓存的数据在总线上保持一致.如果通过嗅探检测到其他处理器打算写内存地址,而这个地址当前处于共享状态,那么正在嗅探的处理器将使它的缓存无效,在下次访问相同的内存地址时,强制执行缓存行填充,也就是从内存中重新读取该内存地址指向的值.


End

网络安全基础入门需要学习哪些知识?

网络安全学习路线

这是一份网络安全从零基础到进阶的学习路线大纲全览,小伙伴们记得点个收藏!

img[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LGlCBkR4-1676651481243)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]编辑

阶段一:基础入门

img[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gbhz22jk-1676651481244)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

网络安全导论

渗透测试基础

网络基础

操作系统基础

Web安全基础

数据库基础

编程基础

CTF基础

该阶段学完即可年薪15w+

阶段二:技术进阶(到了这一步你才算入门)

img[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gTK4nyer-1676651481245)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

弱口令与口令爆破

XSS漏洞

CSRF漏洞

SSRF漏洞

XXE漏洞

SQL注入

任意文件操作漏洞

业务逻辑漏洞

该阶段学完年薪25w+

阶段三:高阶提升

img[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mhGvBEZ0-1676651481247)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

反序列化漏洞

RCE

综合靶场实操项目

内网渗透

流量分析

日志分析

恶意代码分析

应急响应

实战训练

该阶段学完即可年薪30w+

阶段四:蓝队课程

img[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ga5kepEx-1676651481248)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

蓝队基础

蓝队进阶

该部分主攻蓝队的防御,即更容易被大家理解的网络安全工程师。

攻防兼备,年薪收入可以达到40w+

阶段五:面试指南&阶段六:升级内容

img

需要上述路线图对应的网络安全配套视频、源码以及更多网络安全相关书籍&面试题等内容

网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值