volatile是java语言中的一个关键字,常用于并发编程,有两个重要的特点:具有可见性,java虚拟机实现会为其满足Happens before原则;不具备原子性.用法是修饰变量,如:volatile int i.

volatile原理

介绍其可见性先从cpu,cpu缓存和内存的关系入手.

cpu缓存是一种加速手段,cpu查找数据时会先从缓存中查找,如果不存在会从内存中查找,所以如果缓存中数据和内存中数据不一致,cpu处理数据的一致性就无法保证.从机器语言角度来讲,有一些一致性协议来保证缓存一致,但是本文主要从抽象角度解释volatile为何能保证可见性.对于volatile变量的赋值,会刷入主内存,并且通知其他cpu核心,你们缓存中的数据无效了,这样所有cpu核心再想对该volatile变量操作首先会从主内存中重新拉取值.这就保证了对于cpu操作的数据是最新.

但是这并不能保证volatile修饰的变量的原子性.让我们想想一个场景,变量volatile int count存储在内存中,cpu核心1和cpu核心2同时读取该数据,并存入缓存,然后进行count++操作.count++实际可以分解为三步:

int tmp = count;
tmp = count + 1;
count = tmp;

count = tmp执行结束,cpu会把count刷入内存并通知其他cpu缓存无效,如果两个cpu核心同时将其刷入了内存,通知了缓存无效,那么我们是不是只得到了count = 2,是不是丢失了一个+1的值.所以不要试图用volatile保证多步操作的原子性,原子性可以通过synchronized进行维护.

需要注意一点,long类型和double类型的数据长度是64位的,JVM规范允许对于64位类型数据分开赋值,即高位32位和低位32位可以分开赋值,对于这种情况可以使用volatile修饰保证其赋值是一次完成的.但是!!!虽然JVM是这样规定的,绝大多数虚拟机还是实现了64位数据赋值的原子性,即使不使用volatile关键字进行修饰也不会出现读取到只赋值一半的64位类型数据,所以不必要每个longdouble变量之前添加volatile关键字.

感受一下volatile

了解完原理,来通过一段代码感受下volatile.

public class Volatile implements Runnable{
//自增变量i
public /*volatile*/ int i = 0;
@Override
public void run() {
while (true){
i++; //不断自增
}
} public static void main(String[] args) throws InterruptedException {
Volatile vt = new Volatile();
Watcher watcher = new Watcher();
watcher.v = vt;
Thread t1 = new Thread(vt);
Thread t2 = new Thread(watcher);
t1.start();
t2.start();
Thread.sleep(10);
//打印 i 和 s
System.out.println("Volatile.i = " + vt.i + "\nwatcher.w = " + watcher.monitor);
System.exit(0);
}
}
class Watcher implements Runnable{
public Volatile v; public int monitor;
@Override
public void run() {
while (true){
monitor = v.i;//不断将v.i的值赋给s
}
}
}
// 这是未加volatile修饰的输出
Volatile.i = 2517483
watcher.w = 1047805
// 打开volatile注释的输出结果
Volatile.i = 332754
watcher.w = 333354

第一个输出中未加volatile修饰的i的值和watcher读取的值相差太远,

第二个输出中相差就不多了.并且i的值比未加volatile关键字的值差很多,说明对volatile变量的赋值消耗会大一些,不过不用在意,我们很少对volatile关键字进行不断自增操作,一般都是作为状态或者保证对象完整性,而且volatilesynchronized轻量太多了,如果只为了保证可见性,volatile一定是最优选.

哪些场景使用volatile

状态变量

由于boolean的赋值是原子性的,所以volatile布尔变量作为多线程停止标志还简单有效的.

class Machine{
volatile boolean stopped = false; void stop(){stopped = true;}
}

对象完整发布

这里要提到单例对象的双重检查锁,对象完整发布也依赖于happens before原则,有兴趣可以自己去查阅,这个原则是比较啰嗦,可以简单理解为我满足happens before,那么我之前的代码按顺序执行.

public class Singleton {
//单例对象
private static Singleton instance = null;
//私有化构造器,避免外部通过构造器构造对象
private Singleton(){}
//这是静态工厂方法,用来产生对象
public static Singleton getInstance(){
if(instance ==null){
//同步锁防止多次new对象
synchronized (Singleton.class){
//锁内非空判断也是为了防止创建多个对象
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}

这是一个会产生bug的双重检查锁代码,instance = new Singleton()并不是一步完成的,他被分为这几步:

1.分配对象空间;
2.初始化对象;
3.设置instance指向刚刚分配的地址。

下面图中,线程A红色先获得锁,B黄色后进入.

这种情况会出现bug,但是由于volatile满足happens before原则,所以会等对象实例化之后再对地址赋值,我们需要将private static Singleton instance = null;改成private static volatile Singleton instance = null;即可.

其实还有几种场景,如果想了解更多建议阅读IBM的技术社区的文章https://www.ibm.com/developerworks/cn/java/j-jtp06197.html

volatile原理和应用场景的更多相关文章

  1. 【并发编程】Volatile原理和使用场景解析

    目录 一个简单列子 Java内存模型 缓存不一致问题 并发编程中的"三性" 使用volatile来解决共享变量可见性 volatile和指令重排(有序性) volatile和原子性 ...

  2. volatile原理解析

    Java并发编程:volatile关键字解析 volatile 有序性.可见性 volatile可以保证一定程度上有序性,即volatile前面的代码先于后面的代码先执行. 但是前.后代码,各自里面的 ...

  3. Java进阶(七)正确理解Thread Local的原理与适用场景

    原创文章,始自发作者个人博客,转载请务必将下面这段话置于文章开头处(保留超链接). 本文转发自技术世界,原文链接 http://www.jasongj.com/java/threadlocal/ Th ...

  4. ThreadLocal 原理和使用场景分析

    ThreadLocal 不知道大家有没有用过,但至少听说过,今天主要记录一下 ThreadLocal 的原理和使用场景. 使用场景 直接定位到 ThreadLocal 的源码,可以看到源码注释中有很清 ...

  5. 深入解析ThreadLocal 详解、实现原理、使用场景方法以及内存泄漏防范 多线程中篇(十七)

    简介 从名称看,ThreadLocal 也就是thread和local的组合,也就是一个thread有一个local的变量副本 ThreadLocal提供了线程的本地副本,也就是说每个线程将会拥有一个 ...

  6. 蓝牙协议分析(12)_LQ和RSSI的原理及应用场景

    在蓝牙协议栈的物理层,有这样两个比较有用的参数:LQI和RSSI.它们都是通过接收端,判断当前无线环境的质量(链路质量),以指导后续的动作.但这两个数值的计算原理和使用场景又有很大的差别. LQI ( ...

  7. 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?

    在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...

  8. [转帖]Docker五种存储驱动原理及应用场景和性能测试对比

    Docker五种存储驱动原理及应用场景和性能测试对比 来源:http://dockone.io/article/1513 作者: 陈爱珍 布道师@七牛云   Docker最开始采用AUFS作为文件系统 ...

  9. laravel基础课程---7、文件处理、闪存、cookie(cookie原理和使用场景)

    laravel基础课程---7.文件处理.闪存.cookie(cookie原理和使用场景) 一.总结 一句话总结: 页面请求服务器的时候是把这个页面中所有的cookie都带上了的,cookie里面也存 ...

随机推荐

  1. hdu 5101 Select (二分+单调)

    题意: 多多有一个智商值K. 有n个班级,第i个班级有mi个人.智商分别是v1,v2,.....vm. 多多要从这些人中选出两人.要求两人智商和大于K,并且两人不同班.问总共有多少种方案. 数据范围: ...

  2. 聊了聊宏内核和微内核,并吹了一波 Linux

    看这里!!!https://mp.weixin.qq.com/s?__biz=MzI0ODk2NDIyMQ==&mid=2247494048&idx=1&sn=cacfc6a4 ...

  3. 【微服务落地】服务间通信方式: gRPC的入门

    gRPC是什么 官方介绍: https://grpc.io/docs/what-is-grpc/introduction/ "A high-performance, open-source ...

  4. IDEA插件和个性化配置推荐

    插件推荐 我自己现在使用的一些插件和一些自己感觉比较舒服配置分析给大家 idea如何安装插件: 如果打开设置没有看到,直接搜索plugins 然后在这里搜索即可 CodeGlance 小地图 和vsc ...

  5. 服务端渲染技术NUXT

    什么是服务端渲染 ​ 服务端渲染又称SSR (Server Side Render),是在服务端完成页面的内容,而不是在客户端通过AJAX获取数据. 服务器端渲染(SSR)的优势主要在于:更好的 SE ...

  6. 设计模式学习-使用go实现桥接模式

    桥接模式 前言 定义 优点 缺点 应用场景 代码实现 参考 桥接模式 前言 桥接模式的代码实现非常简单,但是理解起来稍微有点难度,并且应用场景也比较局限,所以,相当于代理模式来说,桥接模式在实际的项目 ...

  7. Linux mem 2.5 Buddy 内存回收机制

    文章目录 1. 简介 2. LRU 组织 2.1 LRU 链表 2.2 LRU Cache 2.3 LRU 移动操作 2.3.1 page 加入 LRU 2.3.2 其他 LRU 移动操作 3. LR ...

  8. Linux mem 2.6 Rmap 内存反向映射机制

    文章目录 1. 简介 2. 匿名内存 Rmap 的建立 2.1 fork() 2.2 do_page_fault() 3. 文件内存 Rmap 的建立 3.1 fork() 3.2 do_page_f ...

  9. 第一课 Dubbo背景及原理

    1 . 技术背景 Dubbo每天为2,000+个服务提供3,000,000,000+次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点. Dubbo是一个阿里巴巴开源出来的一个分布式服务框架,致力于 ...

  10. SpringCloud升级之路2020.0.x版-34.验证重试配置正确性(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在前面一节,我们利用 resilience4j 粘合了 OpenFeign 实现了断路器. ...