【Java多线程学习4】volatile关键字及其作用

news2024/11/15 8:02:22

说说对于volatile关键字的理解,及的作用

概述

1、我们知道要想线程安全,就需要保证三大特性:原子性有序性可见性

2、被volatile关键字修饰的变量,可以保证其可见性有序性但是volatile关键字无法保证对变量操作的原子性

  • 可见性:使用volatile修饰变量,就是告诉JVM,这个变量是共享且不稳定的,每次使用它都需要到主存中进行读取
  • 有序性:保证有序性这块主要是指被volatile修饰的关键字,其可以有效的防止变量的指令重排序(通过插入内存屏障的方式来实现防止指令重排序)。

一、volatile如何保证变量的可见性?

被volatile修饰的变量,线程每次读取这样的变量都会从主内存获取最新的值,用于保证自己可以看见其他线程对于该变量的修改并获取到最新的值;而线程每次修改这样的变量也都会同步回写到主内存中,用来保证其他线程访问该变量时可以看到自己对于变量的修改并获取到最新的值。
在这里插入图片描述
在这里插入图片描述

二、volatile关键字如何有效防止指令重排序

在Java中,volatile关键字除了可以保证变量的可见性,还有一个重要的作用就是防止JVM的指令重排序。如果我们将变量声明为volatile修饰的变量,在对这个变量进行读写操作的时候,就会插入特定的内存屏障 (lock前缀指令) 的方式来禁止指令重排序。

最典型的例子就是使用双重校验加锁方式创建单例模式,具体代码如下:

public class SingleObject{
    //volatile关键字防止指令重排序造成的空指针异常(通过插入特定的内存屏障的方式来禁止指令重排序)
    private static volatile SingleObject object;

    //私有构造方法
    private SingleObject() {}

    public static SingleObject getSingleObject() {
        //第一次检查防止每次获取bean都加锁,减小锁的锁的粒度,提升性能
        if (object == null) {
            //加锁,防止第一次创建实例化时,并发线程多次创建对象
            synchronized (SingleObject.class) {
                //第二次检查判断对象没有实例化,则进行对象的实例化
                if (object == null) {
                    object = new SingleObject();
                }
            }
        }
        return object;
    }
}

问题一:object为什么要采用volatile关键字修饰?
object = new SingleObject();这段代码其实分为三步执行:

  • 1、为object分配内存空间。
  • 2、初始化object对象。
  • 3、将object指向分配的内存地址。

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getSingleObject()后发现 object不为空,因此返回 object,但此时 object还未被初始化。

问题二:volatile修饰的变量如何有效防止指令重排序?
volatile关键字通过插入lock前缀形式的内存屏障方式来有效的防止指令重排序。即在分配内存和赋值操作后插入lock前缀指令(内存屏障),等这两步(上述1、2步)执行完成后,再执行3返回object,就可以实现防止指令重排序。

指令重拍导致的问题就是在内存中分配内存并赋值之前将object返回导致空指针异常,现在在这两步中间加了一层lock前缀指令(内存屏障),保证返回singleton之前分配内存赋值等操作执行完就可以防止指令重拍造成的问题了。
在这里插入图片描述

三、volatile关键字能保证原子性吗?

结论:volatile关键字能保证被修饰变量的可见性,但不能保证对变量操作的原子性。

我们直接上代码演示一个例子:

public class practice3 {
    //声明一个被volatile修饰的int型变量
    private static volatile int x = 0;

    //x自增函数
    public static void inc() {
        x++;
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    inc();
                }
            }).start();
        }
        //等待2s保证上述程序执行完。
        Thread.sleep(2000);
        //输出执行后的x的最终值
        System.out.println(x);
    }
}

输出结果如下:
在这里插入图片描述
正常情况下的输出结果是10000,而实际的输出结果却为6984!!!

分析:
为什么会出现上述情况呢,如果说volatile可以保证x++操作的原子性,则每个线程对x变量自增完之后,其他线程可以立即看到修改后的值。10个线程分别进行1000次操作,那么最终x的值应该是10000。

我们可能会误认为x++操作是原子性的操作,其实x++操作是一个复合操作,包括三步:
(1)读取x的值
(2)对x加1
(3)将x的值写回内存
尽管volatile修饰了变量x,但是volatile无法保证上述三步对x操作的原子性,可能出现如下情况:

  • 线程1读取了变量x的值,还没来得及对x进行修改,这时线程2也读取了x变量的值,并对x进行了修改(+1),再将自增后的x的值写回了内存。
  • 线程2执行完毕后,线程1才进行对x的修改(+1),然后再将修改后的值写回内存。
    这样虽然线程1和线程2分别对x执行了自增操作,但实际上x的值只加了1。

如何保证上述代码正确的运行呢?
可以通过synchronized关键字ReentrantLock锁AtomicInteger来保证。

方式1:通过synchronized关键字修饰inc方法实现,具体代码如下

public class practice3 {
    //声明一个被volatile修饰的int型变量
    private static volatile int x = 0;

    //通过synchronzied来修饰x自增函数(****)
    public synchronized static void inc() {
        x++;
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    inc();
                }
            }).start();
        }
        //等待2s保证上述程序执行完。
        Thread.sleep(2000);
        //输出执行后的x的最终值
        System.out.println(x);
    }
}

方式2:通过ReentrantLock锁,锁住inc方法实现,具体代码如下

public class practice3 {
    //声明一个被volatile修饰的int型变量
    private static volatile int x = 0;

    static Lock lock = new ReentrantLock();

    //使用ReentrantLock独占锁锁住x自增操作
    public static void inc() {
        lock.lock();
        try {
            x++;
        } finally {
            lock.unlock();
        }

    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    inc();
                }
            }).start();
        }
        //等待2s保证上述程序执行完。
        Thread.sleep(2000);
        //输出执行后的x的最终值
        System.out.println(x);
    }
}

方式3:使用AtomicInteger实现,具体代码如下
AtomicInteger是JUC包下的工具类,可以实现原子性操作,即保证线程安全。

public class practice3 {
    //声明一个被volatile修饰的int型变量
    private AtomicInteger x = new AtomicInteger();

    //x自增函数
    public static void inc() {
        //获取当前值并+1
        x.getAndIncrement();
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    inc();
                }
            }).start();
        }
        //等待2s保证上述程序执行完。
        Thread.sleep(2000);
        //输出执行后的x的最终值
        System.out.println(x);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/823850.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uniApp 对接安卓平板刷卡器, 读取串口数据

背景: 设备: 鸿合 电子班牌 刷卡对接 WS-B22CS, 安卓11; 需求: 将刷卡器的数据传递到自己的App中, 作为上下岗信息使用, 以完成业务; 对接方式: 1. 厂家技术首先推荐使用 接收自定义广播的方式来获取, 参考代码如下 对应到uniApp 中的实现如下 <template><view c…

python数据可视化Matplotlib

1.绘制简单的折线图 # -*- coding: utf-8 -*- import matplotlib.pyplot as pltinput_values [1, 2, 3, 4, 5] squares [1, 4, 9, 16, 25] plt.style.use(seaborn) fig, ax plt.subplots() ax.plot(input_values, squares, linewidth3) # 线条粗细# 设置图表标题并给坐标…

2023年第四届“华数杯”数学建模思路 - 复盘:光照强度计算的优化模型

文章目录 0 赛题思路1 问题要求2 假设约定3 符号约定4 建立模型5 模型求解6 实现代码 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 问题要求 现在已知一个教室长为15米&#xff0c;宽为12米&#xff0…

less的使用

less的介绍&#xff1a; less使用 1、 less使用的第一种用法&#xff0c;起变量名&#xff0c;变量名区分大小写&#xff1a; 这里我们定义一个粉色变量 我想使用直接把变量拿过来就行 2、vscode使用插件&#xff0c;直接将Css文件转换less文件&#xff1a; 3、第二种用法&…

8.泛型

目录 1 基本使用 2 多个泛型 3 泛型约束 3.1 数组 3.2 extends约束 3.3 用泛型约束泛型 4 泛型接口 5 ts中的数组用的就是泛型 6 泛型类 7 常用泛型工具类型 7.1 让所有属性变为可选属性 Partial 7.2 将所有属性都变为只读属性 Readonly 7.3 从指定类…

git-版本控制器

集中式版本控制工具&#xff08;不常用&#xff09; 版本库集中于中央服务器&#xff0c;team要联网才能工作&#xff08;下载代码&#xff09; SVN CVS 分布式版本控制工具 每个电脑上都有一个完整的版本库&#xff0c;工作时无需联网&#xff0c;可以把修改推送给其他人来…

ThreadLocal有内存泄漏问题吗

对于ThreadLocal的原理不了解或者连Java中的引用类型都不了解的可以看一下我的之前的一篇文章Java中的引用和ThreadLocal_鱼跃鹰飞的博客-CSDN博客 我这里也简单总结一下: 1. 每个Thread里都存储着一个成员变量&#xff0c;ThreadLocalMap 2. ThreadLocal本身不存储数据&…

python爬虫(四)_urllib2库的基本使用

本篇我们将开始学习如何进行网页抓取&#xff0c;更多内容请参考:python学习指南 urllib2库的基本使用 所谓网页抓取&#xff0c;就是把URL地址中指定的网络资源从网络流中读取出来&#xff0c;保存到本地。在Python中有很多库可以用来抓取网页&#xff0c;我们先学习urllib2。…

docker minio安装

1.介绍 Minio是一款开源的对象存储服务&#xff0c;它可以在任何硬件或云平台上提供高性能、高可用性和高安全性的存储解决方案。Minio最新版是2021年11月发布的RELEASE.2021-11-24T23-19-33Z&#xff0c;它带来了以下几个方面的改进和新特性&#xff1a; - 支持S3 Select AP…

Allegro选择暗显模式仍然无法实现暗显模式的解决办法

Allegro选择暗显模式仍然无法实现暗显模式的解决办法 用Allegro进行PCB设计的时候,时常需要使用到暗显模式,让视图中未被高亮的图形暗显下去,如下图 左边是未高亮的网络,右边是已高亮的 但是有时候因为一些原因,导致无法暗显,如下图 下面介绍如何解决这个问题,具体操作…

CSPM认证的价值?

最近 CSPM 证书很热门&#xff0c;含金量高&#xff0c;CSPM证书虽然发起的时间不长&#xff0c;但获取 CSPM 证书也是目前发展的一个趋势。如果打算在项目管理领域发展的强烈建议尽快获取 CSPM&#xff0c;提前为自己积攒一些资本。 一、什么是 CSPM证书&#xff1f;跟PMP是什…

Java-API简析_java.io.FileWriter类(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/132038909 出自【进步*于辰的博客】 因为我发现目前&#xff0c;我对Java-API的学习意识比较薄弱…

elasticsearch 将时间类型为时间戳保存格式的时间字段格式化返回

dsl查询用法如下&#xff1a; GET /your_index/_search {"_source": {"includes": ["timestamp", // Include the timestamp field in the search results// Other fields you want to include],"excludes": []},"query": …

DevOps系列文章之 Docker 安装 NFS 服务器

Docker 安装 NFS 服务器 环境&#xff1a; 192.186.2.105 NFS 服务器 192.168.2.106 Client 客户端 安装 一、服务器端 https://github.com/f-u-z-z-l-e/docker-nfs-server 1、创建目录 mkdir /nfsdata mkdir -p /docker/nfs/2、启动脚本 vim start.sh# 内容 docker run …

ConCurrentHashMap常见面试题

1. JDK1.7和JDK1.8中ConCurrentHashMap的实现有什么不同&#xff1f; JDK1.7中的实现可以认为是大数组套小数组&#xff0c;大数组是Segment数组&#xff0c;小数组是HashEntry数组&#xff0c;锁是锁在大数组的元素上&#xff08;Segment&#xff09;&#xff0c;力度比较大&…

使用vs 2017 C#项目发布

C#项目发布 vs 2017 打包项目源代码 (发布)iis 配置添加ssl 配置 vs 2017 打包项目源代码 (发布) iis 配置 添加ssl 配置 https://help.aliyun.com/zh/ssl-certificate/user-guide/install-ssl-certificates-on-iis-servers

软考A计划-系统集成项目管理工程师-项目沟通管理-上

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

Java反射(三)

目录 1.反射与代理设计模式 2.反射与Annotation 3.自定义Annotation 4.Annotation整合工厂设计模式和代理设计模式 1.反射与代理设计模式 代理模式是指通过业务真实类实现业务接口&#xff0c;再通过设置代理类创建业务真实类子类从而间接访问业务真实类。但是这存在一个弊…

助你丝滑过度到 Vue3 组合式Api的优势新的组件 ②⑧

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; VUE3~TS &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f449;…

centos系统离线安装k8s v1.23.9最后一个版本并部署服务,docker支持的最后一个版本

注意&#xff1a;我这里的离线安装包是V1.23.9. K8S v1.23.9离线安装包下载&#xff1a; 链接&#xff1a;https://download.csdn.net/download/qq_14910065/88143546 这里包括离线安装所有的镜像&#xff0c;kubeadm&#xff0c;kubelet 和kubectl&#xff0c;calico.yaml&am…