在一些开源的框架的源码当中时不时都可以看到volatile这个关键字,最近特意学习一下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关键字,也是不能保证安全的。

关注我

以你最方便的方式关注我:

微信公众号:

精通Java中的volatile关键字的更多相关文章

  1. java中的volatile关键字

    java中的volatile关键字 一个变量被声明为volatile类型,表示这个变量可能随时被其他线程改变,所以不能把它cache到线程内存(如寄存器)中. 一般情况下volatile不能代替syn ...

  2. Java中的volatile关键字的功能

    Java中的volatile关键字的功能 volatile是java中的一个类型修饰符.它是被设计用来修饰被不同线程访问和修改的变量.如果不加入volatile,基本上会导致这样的结果:要么无法编写多 ...

  3. 深入理解Java中的volatile关键字

    在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...

  4. java中的Volatile关键字使用

    文章目录 什么时候使用volatile Happens-Before java中的Volatile关键字使用 在本文中,我们会介绍java中的一个关键字volatile. volatile的中文意思是 ...

  5. Java中的volatile关键字为什么不是不具有原子性

    Java中long赋值不是原子操作,因为先写32位,再写后32位,分两步操作,而AtomicLong赋值是原子操作,为什么?为什么volatile能替代简单的锁,却不能保证原子性?这里面涉及volat ...

  6. CPU缓存一致性协议与java中的volatile关键字

    有关缓存一致性协议MESI自行百度. 提出问题:volatile在缓存一致性协议上又做了哪些事情?为啥它不保证原子性? 在缓存一致性协议下,CPU为了执行效率使用了写(存储)缓存和失效队列从而导致对用 ...

  7. zz剖析为什么在多核多线程程序中要慎用volatile关键字?

    [摘要]编译器保证volatile自己的读写有序,但由于optimization和多线程可以和非volatile读写interleave,也就是不原子,也就是没有用.C++11 supposed会支持 ...

  8. java 轻量级同步volatile关键字简介与可见性有序性与synchronized区别 多线程中篇(十二)

    概念 JMM规范解决了线程安全的问题,主要三个方面:原子性.可见性.有序性,借助于synchronized关键字体现,可以有效地保障线程安全(前提是你正确运用) 之前说过,这三个特性并不一定需要全部同 ...

  9. 再议Java中的static关键字

    再议Java中的static关键字 java中的static关键字在很久之前的一篇博文中已经讲到过了,感兴趣的朋友可以参考:<Java中的static关键字解析>. 今天我们再来谈一谈st ...

随机推荐

  1. am335x system upgarde ddr3 capacity configuration base on TI DDR3 Software Leveling Tool (二十)

    follow test is  use ti DDR Software Leveling op log. AM335x DDR3 Software Leveling -- Version: Beta ...

  2. 彩色模型,CIE XYZ,CIE RGB

    学习DIP第8天 转载请标明出处:http://blog.csdn.net/tonyshengtan,欢迎大家转载,发现博客被某些论坛转载后,图像无法正常显示,无法正常表达本人观点,对此表示很不满意. ...

  3. google中select添加onclick

    有下拉跳转框如下所示: <select name="page" size="1" > <option onclick="refurb ...

  4. Hadoop元数据备份与恢复方案

    Hadoop元数据备份与恢复方案 标签(空格分隔): Hadoop Namenode 备份策略: 周期性备份namenode.standby namenode的dfs.namenode.name.di ...

  5. 课下选做作业MySort

    20175227张雪莹 2018-2019-2 <Java程序设计> 课下选做作业MySort 要求 注意:研究sort的其他功能,要能改的动代码,需要答辩 模拟实现Linux下Sort ...

  6. 炉石兄弟 修复图腾师问题 by大神beebee102, 还有阴燃电鳗

    修复图腾师问题   beebee102 修复了先摇图腾再下图腾师的问题,另外加了阴燃电鳗的sim卡.在兄弟策略的模拟程序中测试了没问题,真机没有试过,麻烦吧友测试了回复一下.链接: [有效] http ...

  7. Flume-安装与 NetCat UDP Source 监控端口

    Flume 文档:https://flume.apache.org/FlumeUserGuide.html Flume 下载:https://archive.apache.org/dist/flume ...

  8. python——装饰器(不定长参数,闭包,装饰器)示例

    def func(functionName): print("正在装饰") def func_in(*args, **kargs): print("------func_ ...

  9. Hibernate3运行原理

    Hibernate的运行过程如下: 1.应用程序先调用Configuration类,该类读取Hibernate配置文件及映射文件中的信息,2.并用这些信息生成一个SessionFactory对象,3. ...

  10. 一百一十二:CMS系统之前台用户模型

    安装shortuuid用于前台用户的主键:pip install shortuuid 创建模型 from datetime import datetimeimport enumfrom werkzeu ...