一文精通Java中的volatile关键字
作者:架构与我 发布时间:2023-11-24 04:17:14
前言
在一些开源的框架的源码当中时不时都可以看到volatile这个关键字,最近特意学习一下volatile关键字的使用方法。
volatile 关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见。 相较于 synchronized 是一种较为轻量级的同步策略。
缺点:
1. volatile 不具备“互斥性”
2. volatile 不能保证变量的“原子性”
很多资料中是这样介绍volatile关键字的:
volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
文字不太好理解,通过例子来理解。
1、例子
首先看一个没有使用volatile关键字例子:
package com.swnote.java;
/**
* volatile测试例子
*
* @author lzj
* @date [2019-04-47]
*/
public class VolatileTest {
private boolean flag;
public static void main(String[] args) {
VolatileTest test = new VolatileTest();
test.test();
}
public void test() {
new Thread(() -> {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
}).start();
new Thread(() -> {
while (true) {
if (flag) {
System.out.println("thread flag = " + flag);
}
}
}).start();
}
}
该例子中定义了一个flag共享变量,test方法里面开启了两个线程,第一个线程在等待1秒中后修改共享变量flag的值为true,第二个线程通过循环判断flag的值,当flag的值为true时,输出内容。
此时有两种猜想:
执行后,可以看到输出内容,即说明第二个线程能够感知到第一个线程对共享变量flag的修改
执行后,没有任务内容,即说明第二个线程无法感知到第一个线程对共享变量flag的修改
然后执行结果为:
没有任务的输出内容,即证明了此时这样情况下第二个线程无法感知到第一个线程对共享变量flag的修改的
现在修改一下例子,即为flag变量加上volatile关键字,即:
private volatile boolean flag;
然后再运行,此时结果为:
此时就有内容输出了,说明加上volatile关键字后,第二个线程可以感知到第一个线程对共享变量flag的修改的,这就是上面概念中所说的volatile在多处理器开发中保证了共享变量的“可见性”。
2、原理
通过上面的例子证明了volatile在多处理器开发中保证了共享变量的“可见性”,那它是怎么实现的呢?
这时就得介绍一下Java的内存模型了,首先看如下示意图:
Java内存模型是如上面所示的:
共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存保存了被该线程使用到的主内存的副本拷贝,线程对变量的所有操作都必须在自己的本地内存中进行,而不能直接读写主内存中的变量。
根据此理解,上述例子的在没有加volatile时的情况是这样的:
第一个线程从主内存中获取共享变量flag的值,此时值为false,将该值放到自己的本地内存中,然后对变量进行修改,将值改为true,此时也只是将本地内存中flag的值改为了true,此时还没有将值同步到主内存中,然后第二线程也是将共享变量flag的值放到自己的本地内存中,而此时flag的值还是为false,所以就是一直没有内容输出了。
然而加上volatile关键字后,第一个线程对flag的修改会强制刷新到主内存中去,同时还会导致其他线程中的本地内存的值会无效,需要重新到主内存获取,这样就保证了第一个线程对flag修改后,第二线程能够感知到。
3、注意点
volatile是轻量级的synchronized,但是它是不能够代替synchronized的,因为volatile只能保证原子性操作的安全,对于复合操作,volatile是不能保证线程安全的。
例如:
package com.swnote.java;
/**
* 复合操作例子
*
* @author lzj
* @date [2019-04-27]
*/
public class StatisticTest {
private volatile int num = 0;
public static void main(String[] args) {
StatisticTest test = new StatisticTest();
test.statistic();
}
public void statistic() {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
num++;
}).start();
}
System.out.println("num = " + num);
}
}
期望的运行结果是20,可是几乎每次运行结果都是不一样的,例如有的结果为:
这是因为num++这个操作不是原子性的,所以即使使用了volatile关键字,也是不能保证安全的。
来源:https://www.cnblogs.com/atcloud/p/10819111.html


猜你喜欢
- 有一位程序员去相亲的时候,非常礼貌得说自己是一名程序员,并解释自己是做底层架构的,于是女方听到"底层"两个字,就一脸嫌弃
- 本文实例分析了Android多线程。分享给大家供大家参考,具体如下:在Android下面也有多线程的概念,在C/C++中,子线程可以是一个函
- 详解Kotlin Android开发中的环境配置在Android Studio上面进行安装插件在Settings ->Plugins
- 1. 出故障了没办法,干it这一行,就得天天面对故障,大家就是传说中的消防员,到处救火。不过,这次的故障范围有点大,宿主机都打不开了。好在监
- 啥都不说先上效果图,这个是我项目里的效果:下面的是我抽取出来的 demo 适配啥的我基本上都做好了没做其他的ok 下面 说一下思路把首先 说
- 客户端代码:/// <summary>/// 批量上传图片/// </summary>/// <param n
- Spring AOP proxyTargetClass的行为要点列表形式proxyTargetClasstrue目标对象实现了接口 – 使用
- 昨天下午快下班的时候,无意中听到公司两位同事在探讨批量向数据库插入数据的性能优化问题,顿时来了兴趣,把自己的想法向两位同事说了一下,于是有了
- 用过iphone的朋友相信都体验过页面上拉下拉有一个弹性的效果,使用起来用户体验很好;Android并没有给我们封装这样一个效果,我们来看下
- 本文实例讲述了C#判断一天、一年已经过了百分之多少的方法。分享给大家供大家参考。具体如下:这里写了四个函数,分别是1.判断当前时间过了今天的
- 文件上传也是常见的功能,趁着周末,用Spring boot来实现一遍。前端部分前端使用jQuery,这部分并不复杂,jQuery可以读取表单
- 性能测试过程中,如果进行大量的并发时,界面容易卡死。通过非GUI(命令行)的方式是个不错的选择。windows环境1.在安装Jmeter的目
- 本文实例为大家分享了C#实现餐厅管理系统的具体代码,供大家参考,具体内容如下部分代码:fm_change_password.csusing
- 官方的C/C++插件是支持使用.clang-format配置文件进行自定义风格代码格式化的,无需另外安装clang-format插件。但是使
- 1.什么是继承?(1)用来提高代码的复用性(2)将相同的功能模块封装到方法中需要使用时调用方法达到代码复用的目的(3)通过继承将多个类中的相
- 背景介绍你刚从学校毕业后,到新公司实习,试用期又被毕业,然后你又不得不出来面试,好在面试的时候碰到个美女面试官!面试官: 小伙子,
- 话不多说,请看实例代码String ip = request.getHeader("x-forwarded-for");
- indexof方法:注解:indexOf 方法返回一个整数值,指出 String 对象内子字符串的开始位置。如果没有找到子字符串,则返回-1
- 有项目需求需要绘制多个圆圈,并且使用连续的数字对其排列起来,也就是好多排的圆圈。首先看一下效果图:一排设置为8个,一共有53个的:一排设值为
- 第一种方法string s=abcdeabcdeabcde;string[] sArray=s.Split('c') ;fo