关于 volatile——可见性,有序性,内存屏障

2023-10-29

并发编程的三大特性:原子性,有序性,可见性。从这三个方面去看一下 volatile。


volatile 保证了可见性:

public class Demo1 {

    private  boolean flag = true;

    public void test(){
        while (flag){
            
        }

        System.out.println("test finish");
    }

    public void setFlag(boolean flag){
        this.flag = flag;
    }

    public static void main(String[] args) {
        Demo1 demo1 = new Demo1();

        new Thread(()->{
            demo1.test();
        }).start();

        new Thread(()->{
            demo1.setFlag(false);
        }).start();
    }
}

事实上,控制台不会输出任何东西,程序能运行到电脑没电。改为下面这样则会正常运行。

private volatile boolean flag = true;

使用 volatile 保证了共享变量在线程之间的可见性。


volatile 不能保证原子性:

再看下面这段代码,10个线程对num执行++操作,直到num=100。volatile 并不能保证结果的正确性,结果有可能大于 100。

public class Demo2 {

    private volatile int num = 0;

    public void add() throws InterruptedException {
        while (num < 100){
            //Thread.sleep(10);
            num++; 
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Demo2 demo2 = new Demo2();

        for (int i=0; i<10; i++){
            new Thread(()->{
                demo2.add();//方便阅读,没捕获异常
            }).start();
        }

        Thread.sleep(2000);

        System.out.println(demo2.num);
    }
}

假如 在num=99 时 thread1 进入 while 条件,由于 num++ 不是原子性操作,在执行 num++ 时 cpu 时间片用完而切换到 thread2,此时num 还是99,thread2 进行num++ 后,继续执行 thread1, num再 +1,结果错误。可以拿掉注释,放大这种情况。

public  void add() throws InterruptedException {
    while (num < 100){
        Thread.sleep(10);
        num++;
    }
}

所以可以考虑把 num++ 变为原子操作 优化成以下方式:

//AtomicInteger 保证了原子性和可见性
private AtomicInteger num = new AtomicInteger(0);

public void add() throws InterruptedException {
    while (num.get() < 100){
        //1. Thread.sleep(10); 
        num.incrementAndGet();
    }
}

插一句:AtomicInteger 涉及到 CAS,关于 CAS可以看我这篇文章(下一篇写)。

这样就正确了吗?其实并没有。因为进入 while 后且在执行 incrementAndGet() 之前,有可能时间片用完而切换到其他线程,也就是说,进入 while 之后的操作不是原子性的操作。可以拿掉1处的注释来放大这种现象。

其实这段代码很奇葩,基本上没人会这么写,但是可以通过代码学习到很多东西。让我们继续优化:

思路就是:怎么让程序进入 while之后的操作变为原子操作?第一个想到的就是 snychronizd,但是就这段代码来说,加上snychronizd,就会变成只会有一个线程执行,这并不是本意。当然可以手动释放 snychronizd 占有的锁,可以通过 抛异常、break、return,而且 snychronizd 会影响性能,这会显得很笨重。

那 snychronizd 就排除在外了,有了这个思路,方法就显而易见了,可以用显式锁来保证原子性:

private AtomicInteger  num = new AtomicInteger(0);
private Lock lock = new ReentrantLock(false);

public void add() throws InterruptedException {
    while (num.get() < 100){
        lock.lock();

        if (num.get() < 100){ 
            num.incrementAndGet();
        }

        lock.unlock();
    }
}

volatile 保证了有序性:

懒汉式单例问题:

public class SingletonDoubleCheck{

    private SingletonDoubleCheck singleton;

    private SingletonDoubleCheck(){}

    public static SingletonDoubleCheck getInstance(){
        if (singleton == null){
            singleton = new SingletonDoubleCheck();     
        }

        return singleton;
    }
}

这样写会造成极大的安全隐患,高并发条件下,如果有两个线程同时运行,都满足 if 条件,然后对对象进行了两次初始化,不能保证单例的唯一性。

所以考虑进行同步优化:

public class SingletonDoubleCheck{

    private static SingletonDoubleCheck singleton;

    private SingletonDoubleCheck(){}

    public static SingletonDoubleCheck getInstance(){
        //只在没进行初始化之前进行,初始化过后,就不在进入,提高性能
        if (singleton == null){
            //同步,同步快内的程序可以看做原子操作
            synchronized (SingletonDoubleCheck.class){
                //再判断一次,防止多个线程同时进入后多次初始化
                if (singleton == null){
                    singleton = new SingletonDoubleCheck();
                }
            }
        }

        return singleton;
    }
}

这样看似已经很完美,其实在实际运行的过程中可能会抛出 NullPointerException。因为在实际运行过程中 Jvm 为了提高性能 会对指令进行重排序。

简单说一下重排序:假如在对象对象需要 A、B 两个单两个资源,构造函数中需要实例化 A、B 两个资源和自身的对象,有可能先对自身对象先进行实例化,而此时 A、B 并没有完成实例化。此时会造成 NullPointerException。

所以改为:

private volatile static SingletonDoubleCheck singleton;

volatile 会形成内存屏障,而禁用区域内的指令重排序:

关于内存屏障可以 戳这篇 and 这篇

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

关于 volatile——可见性,有序性,内存屏障 的相关文章

  • java.lang.NoClassDefFoundError:org.apache.batik.dom.svg.SVGDOMImplementation

    我在链接到我的 Android LibGDX 项目的 Apache Batik 库时遇到了奇怪的问题 但让我们从头开始 在 IntelliJ Idea 中我有一个项目 其中包含三个模块 Main Android 和 Desktop 我强调的
  • Play框架运行应用程序问题

    每当我尝试运行使用以下命令创建的新 Web 应用程序时 我都会收到以下错误Play http www playframework org Error occurred during initialization of VM Could no
  • 在 java 类和 android 活动之间传输时音频不清晰

    我有一个android活动 它连接到一个java类并以套接字的形式向它发送数据包 该类接收声音数据包并将它们扔到 PC 扬声器 该代码运行良好 但在 PC 扬声器中播放声音时会出现持续的抖动 中断 安卓活动 public class Sen
  • 使用 Android 发送 HTTP Post 请求

    我一直在尝试从 SO 和其他网站上的大量示例中学习 但我无法弄清楚为什么我编写的示例不起作用 我正在构建一个小型概念验证应用程序 它可以识别语音并将其 文本 作为 POST 请求发送到 node js 服务器 我已确认语音识别有效 并且服务
  • 在 HTTPResponse Android 中跟踪重定向

    我需要遵循 HTTPost 给我的重定向 当我发出 HTTP post 并尝试读取响应时 我得到重定向页面 html 我怎样才能解决这个问题 代码 public void parseDoc final HttpParams params n
  • Final字段的线程安全

    假设我有一个 JavaBeanUser这是从另一个线程更新的 如下所示 public class A private final User user public A User user this user user public void
  • INSERT..RETURNING 在 JOOQ 中不起作用

    我有一个 MariaDB 数据库 我正在尝试在表中插入一行users 它有一个生成的id我想在插入后得到它 我见过this http www jooq org doc 3 8 manual sql building sql statemen
  • Android MediaExtractor seek() 对 MP3 音频文件的准确性

    我在使用 Android 时无法在eek 上获得合理的准确度MediaExtractor 对于某些文件 例如this one http www archive org download emma solo librivox emma 01
  • JavaMail 只获取新邮件

    我想知道是否有一种方法可以在javamail中只获取新消息 例如 在初始加载时 获取收件箱中的所有消息并存储它们 然后 每当应用程序再次加载时 仅获取新消息 而不是再次重新加载它们 javamail 可以做到这一点吗 它是如何工作的 一些背
  • Liferay ClassNotFoundException:DLFileEntryImpl

    在我的 6 1 0 Portal 实例上 带有使用 ServiceBuilder 和 DL Api 的 6 1 0 SDK Portlet 这一行 DynamicQuery query DynamicQueryFactoryUtil for
  • 禁止的软件包名称:java

    我尝试从数据库名称为 jaane 用户名 Hello 和密码 hello 获取数据 错误 java lang SecurityException Prohibited package name java at java lang Class
  • 使用Caliper时如何指定命令行?

    我发现 Google 的微型基准测试项目 Caliper 非常有趣 但文档仍然 除了一些示例 完全不存在 我有两种不同的情况 需要影响 JVM Caliper 启动的命令行 我需要设置一些固定 最好在几个固定值之间交替 D 参数 我需要指定
  • 加密 JBoss 配置中的敏感信息

    JBoss 中的标准数据源配置要求数据库用户的用户名和密码位于 xxx ds xml 文件中 如果我将数据源定义为 c3p0 mbean 我会遇到同样的问题 是否有标准方法来加密用户和密码 保存密钥的好地方是什么 这当然也与 tomcat
  • 仅将 char[] 的一部分复制到 String 中

    我有一个数组 char ch 我的问题如下 如何将 ch 2 到 ch 7 的值合并到字符串中 我想在不循环 char 数组的情况下实现这一点 有什么建议么 感谢您花时间回答我的问题 Use new String value offset
  • 如何从终端运行处理应用程序

    我目前正在使用加工 http processing org对于一个小项目 但是我不喜欢它附带的文本编辑器 我使用 vim 编写所有代码 我找到了 pde 文件的位置 并且我一直在从 vim 中编辑它们 然后重新打开它们并运行它们 重新加载脚
  • 如何从指定日期获取上周五的日期? [复制]

    这个问题在这里已经有答案了 如何找出上一个 上一个 星期五 或指定日期的任何其他日期的日期 public getDateOnDay Date date String dayName 我不会给出答案 先自己尝试一下 但是 也许这些提示可以帮助
  • Java列表的线程安全

    我有一个列表 它将在线程安全上下文或非线程安全上下文中使用 究竟会是哪一个 无法提前确定 在这种特殊情况下 每当列表进入非线程安全上下文时 我都会使用它来包装它 Collections synchronizedList 但如果不进入非线程安
  • simpleframework,将空元素反序列化为空字符串而不是 null

    我使用简单框架 http simple sourceforge net http simple sourceforge net 在一个项目中满足我的序列化 反序列化需求 但在处理空 空字符串值时它不能按预期工作 好吧 至少不是我所期望的 如
  • 当我从 Netbeans 创建 Derby 数据库时,它存储在哪里?

    当我从 netbeans 创建 Derby 数据库时 它存储在哪里 如何将它与项目的其余部分合并到一个文件夹中 右键单击Databases gt JavaDB in the Service查看并选择Properties This will
  • 节拍匹配算法

    我最近开始尝试创建一个移动应用程序 iOS Android 它将自动击败比赛 http en wikipedia org wiki Beatmatching http en wikipedia org wiki Beatmatching 两

随机推荐

  • Win10安装Vc++6.0 卡在“安装程序正在更新您的系统”解决方法

    Win10安装Vc 6 0 卡在 安装程序正在更新您的系统 解决方法 点击链接
  • c++: 单例模式(Singleton)的最优写法

    目的 本例简介C 中单例模式的最优写法 实现 基础写法 下面的代码是C 单例的基础写法 在静态函数Singleton getInstance 中定义了Singleton的静态变量对象 并返回此对象的引用 由于C 函数的静态变量唯一性 可以确
  • Neo4j的下载与安装

    1 官网下载地址 https neo4j com download center 下载完成后可进行解压 2 Neo4j应用程序有如下主要的目录结构 bin目录 用于存储Neo4j的可执行程序 conf目录 用于控制Neo4j启动的配置文件
  • Servlet中会话管理

    Servlet提供了session和cookie进行会话管理 其中session是保存在服务器端的 而cookie是保存在客户端的 Session Servlet规范中 提供了三种机制进行会话跟踪 SSL会话 Cookies URL重写 S
  • 【满分】【华为OD机试真题2023 JS】优雅数组

    华为OD机试真题 2023年度机试题库全覆盖 刷题指南点这里 优雅数组 知识点双指针数组滑窗 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 如果一个数组中出现次数最多的元素出现大于等于k次 被称为 k 优雅数组 k也可以被
  • 用vscode运行Java程序初体验

    最近开始学习Java编程了 以前学习过C C Python 主要用微软的visual studio code来运行python程序 于是就尝试了用vscode来运行java代码 记录一下使用的经验 帮助大家少走弯路 安装了Java的集成编辑
  • Nginx之 location 详解

    文章目录 一 知识准备 二 简介 三 详解 1 location语法规则 2 分类 3 匹配优先级 4 举例 一 知识准备 需要掌握Nginx 的基本知识 同时对nginx conf 有一定的了解 二 简介 Nginx 是一款反向代理工具
  • Django图书商城系统实战开发-用户认证和授权

    总结一下 在实战开发Django图书商城系统中 用户认证和授权是关键的技术需求 以下是你需要掌握的技术要点 熟悉Django框架的基本结构和设计模式 使用Django认证系统实现用户的注册 登录和注销功能 扩展Django的用户模型 存储额
  • 分支限界算法的多线程实现

  • 信息安全导论 实验四 RSA算法(不要求支持大数)

    一 实验目的与原理 这次次实验主要难点在于以下三个算法的理解与实现 1 Rabin Miller 算法 2 扩展欧几里得算法 3 快速幂取余算法 根据前面的算法 我们知道明文和密文都不能大于 n 假设 n 的长度为 L 对于 明文 我们需要
  • Halcon 算子 get_contour_global_attrib_xld

    作用 返回XLD轮廓的全局属性值 参数翻译 输入轮廓 输出轮廓 需要获取的属性名称 获取的属性值 描述 get contour global attrib xld返回XLD轮廓的全局属性名的值 全局属性是为每个轮廓定义的附加值 属性名 Na
  • Pytorch实现手写数字识别【基于卷积神经网络】

    说明 本案例在上一次案例的基础之上对神经网络模型进行了修改 在全连接层之前加入了特征提取层 卷积层 池化层 其他基本保持一致 卷积神经网络模型设计如下 1 导入各类需要的包 import torch import numpy as np 导
  • 3次 int 关系运算 比较两个非负 float 变量的大小

    include
  • 数据库应用:MySQL主从复制与读写分离

    目录 一 理论 1 读写分离 2 MySQL主从复制延迟原因和优化方法 3 MySQL主从复制的几个同步模式 4 MySQL读写分离 5 主从复制 异步复制 6 主从复制 半同步复制 7 读写分离 使用Amoeba 二 实验 1 主从复制
  • 帧是什么?

    数据在网络上是以很小的称为帧 Frame 的单位传输的 帧由几部分组成 不同的部分执行不同的功能 数据在网络上是以很小的称为帧 Frame 的单位传输的 帧由几部分组成 不同的部分执行不同的功能 帧通过特定的称为网络驱动程序的软件进行成型
  • 微信公众号消息与事件处理机制

    一 授权事件接收URL 1 微信服务器每隔10分钟定时推送component verify ticket 第三方平台方在收到ticket推送后也需进行解密 ComponentVerifyTicket ticket fxUU8P4 ip5B
  • sql语句group by以及count()的一些思考

    admin表 student表 第一条sql语句 SELECT admin id student name FROM admin student 结果 直接把admin id与student name匹配的所有记录 即0 1 2 3 4 5
  • 华为OD机试-MVP争夺战

    题目描述 在星球争霸篮球赛对抗赛中 强大的宇宙战队 希望每个人都能拿到MVP MVP的条件是 单场最高分得分获得者 可以并列 所以宇宙战队决定在比赛中 尽可能让更多的队员上场 且让所有有得分的队员得分都相同 然而比赛过程中的每一分钟的得分都
  • SpringCloud-消息总线

    消息总线 Spring Cloud Bus 概述 一言以蔽之 分布式自动刷新配置功能 Spring Cloud Bus配合Spring Cloud Config使用可以实现配置的动态刷新 是什么 Spring Cloud Bus 配合Spr
  • 关于 volatile——可见性,有序性,内存屏障

    并发编程的三大特性 原子性 有序性 可见性 从这三个方面去看一下 volatile volatile 保证了可见性 public class Demo1 private boolean flag true public void test