写点什么

Volatile 原理七:volatile 都不保证原子性,为啥我们还要用它

作者:悟空聊架构
  • 2021 年 12 月 15 日
  • 本文字数:1209 字

    阅读完需:约 4 分钟

volatile 都不保证原子性,为啥我们还要用它?

奇怪的是,volatile 都不保证原子性,为啥我们还要用它?


volatile 是轻量级的同步机制,对性能的影响比 synchronized 小。


典型的用法:检查某个状态标记以判断是否退出循环。


比如线程试图通过类似于数绵羊的传统方法进入休眠状态,为了使这个示例能正确执行,asleep 必须为 volatile 变量。否则,当 asleep 被另一个线程修改时,执行判断的线程却发现不了。


那为什么我们不直接用 synchorized,lock 锁?它们既可以保证可见性,又可以保证原子性为何不用呢?


因为 synchorized 和 lock 是排他锁(悲观锁),如果有多个线程需要访问这个变量,将会发生竞争,只有一个线程可以访问这个变量,其他线程被阻塞了,会影响程序的性能。


注意:当且仅当满足以下所有条件时,才应该用 volatile 变量

  • 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。

  • 该变量不会与其他的状态一起纳入不变性条件中。

  • 在访问变量时不需要加锁。

volatile 常见应用

这里举一个应用,双重检测锁定的单例模式


package com.jackson0714.passjava.threads;/** 演示volatile 单例模式应用(双边检测) * @author: 悟空聊架构 * @create: 2020-08-17 */
class VolatileSingleton { private static VolatileSingleton instance = null; private VolatileSingleton() { System.out.println(Thread.currentThread().getName() + "\t 我是构造方法SingletonDemo"); } public static VolatileSingleton getInstance() { // 第一重检测 if(instance == null) { // 锁定代码块 synchronized (VolatileSingleton.class) { // 第二重检测 if(instance == null) { // 实例化对象 instance = new VolatileSingleton(); } } } return instance; }}
复制代码


代码看起来没有问题,但是 instance = new VolatileSingleton();其实可以看作三条伪代码:


memory = allocate(); // 1、分配对象内存空间instance(memory); // 2、初始化对象instance = memory; // 3、设置instance指向刚刚分配的内存地址,此时instance != null
复制代码


步骤 2 和 步骤 3 之间不存在 数据依赖关系,而且无论重排前 还是重排后,程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。


memory = allocate(); // 1、分配对象内存空间instance = memory; // 3、设置instance指向刚刚分配的内存地址,此时instance != null,但是对象还没有初始化完成instance(memory); // 2、初始化对象
复制代码


如果另外一个线程执行:if(instance == null) 时,则返回刚刚分配的内存地址,但是对象还没有初始化完成,拿到的 instance 是个假的。如下图所示:



解决方案:定义 instance 为 volatile 变量


private static volatile VolatileSingleton instance = null;
复制代码


发布于: 3 小时前阅读数: 5
用户头像

用故事、大白话讲解Java、分布式、架构设计 2018.05.06 加入

公众号:「悟空聊架构」 【个人博客】www.passjava.cn 【开源项目】基于 SpringCloud 的一套面试刷题系统 【Github】https://github.com/Jackson0714/PassJava-Platform

评论

发布
暂无评论
Volatile 原理七:volatile都不保证原子性,为啥我们还要用它