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

说说对于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);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

实验-路由器配置静态路由

软件&#xff1a;cicso packet tracer 8.0 拓扑图&#xff1a;路由器&#xff1a;Router-PT、连接线&#xff1a;Serial DTE、连接口&#xff1a;Serial口&#xff08;serial是串行口,一般用于连接设备,不能连接电脑&#xff09; 实验步骤&#xff1a; 1、构建拓扑图&#xf…

pytorch学习——正则化技术——权重衰减

一、概念介绍 权重衰减&#xff08;Weight Decay&#xff09;是一种常用的正则化技术&#xff0c;它通过在损失函数中添加一个惩罚项来限制模型的复杂度&#xff0c;从而防止过拟合。 在训练参数化机器学习模型时&#xff0c; 权重衰减&#xff08;weight decay&#xff09;是…

windows环境下adb 下载和配置,连接手机。

ADB下载地址&#xff1a; https://adbdownload.com/ 选择下载windows系统的。 下载后解压&#xff0c;查看adb.exe所在的目录&#xff0c;如下 这里将路径复制下来&#xff1a;D:\ADB 配置到系统环境变量中。 然后再打开cmd&#xff0c;输入adb version查看版本。 出现…

2023.8.1号论文阅读

文章目录 MCPA: Multi-scale Cross Perceptron Attention Network for 2D Medical Image Segmentation摘要本文方法实验结果 SwinMM: Masked Multi-view with SwinTransformers for 3D Medical Image Segmentation摘要本文方法实验结果 MCPA: Multi-scale Cross Perceptron Att…

极简在线商城系统,支持docker一键部署

Hmart 给大家推荐一个简约自适应电子商城系统&#xff0c;针对虚拟商品在线发货&#xff0c;支持企业微信通知&#xff0c;支持docker一键部署&#xff0c;个人资质也可搭建。 前端 后端 H2 console 运行命令 docker run -d --name mall --restartalways -p 8080:8080 -e co…

10. Mybatis 项目的创建

目录 1. Mybatis 概念 2. 第一个 Mybits 查询 2.1 创建数据库和表 2.2 添加 Mybatis 框架支持 2.3 添加配置文件 2.4 配置 MyBatis 中的 XML 路径 2.5 添加业务代码 在学习 Mybatis 之前&#xff0c;我们需要知道 Mybatis 和 Spring 没有任何的关系。如果一定要强调二者…

ChatGPT安全技术

前言 近期&#xff0c;Twitter 博主 lauriewired 声称他发现了一种新的 ChatGPT"越狱"技术&#xff0c;可以绕过 OpenAI 的审查过滤系统&#xff0c;让 ChatGPT 干坏事&#xff0c;如生成勒索软件、键盘记录器等恶意软件。 他利用了人脑的一种"Typoglycemia&q…

Kubernetes系列

文章目录 1 详解docker,踏入容器大门1.1 引言1.2 初始docker1.3 docker安装1.4 docker 卸载1.5 docker 核心概念和底层原理1.5.1 核心概念1.5.2 docker底层原理 1.6 细说docker镜像1.6.1 镜像的常用命令 1.7 docker 容器1.8 docker 容器数据卷1.8.1 直接命令添加1.8.2 Dockerfi…

远程控制平台四之优化部署

服务器端打包 把服务器打成jar包对于后台开发的朋友来说小菜一碟,但对于前端开发可能有些细节要注意一下,尤其是有依赖其他第三方库的情况下,这里梳理了一下流程: File – Project Structure – Artifacts – add – JAR – From modules and dependencies 选中module和主…

OpenCVForUnity(十)扩张与侵蚀效果

文章目录 前言扩张案例展示 侵蚀案例展示 结语&#xff1a; 前言 在这个教程中&#xff0c;您将学习两种常见的图像形态运算符&#xff1a;侵蚀和膨胀。为此&#xff0c;您将使用OpenCV库中的两个函数&#xff1a;erode 和 dilate。 形态操作是一组基于形状的图像处理操作。形态…

电气防火限流式保护器在汽车充电桩使用上的作用

【摘要】 随着电动汽车行业的不断发展&#xff0c;电动汽车充电设施的使用会变得越来越频繁和广泛。根据中汽协数据显示&#xff0c;2022年上半年&#xff0c;我国新能源汽车产销分别完成266.1万辆和260万辆,同比均增长1.2倍,市场渗透率达21.6%。因此&#xff0c;电动汽车的安全…

【MySQL】数据库基本使用

文章目录 一、数据库介绍二、数据库使用2.1 登录MySQL2.2 基本使用2.2.1 显示当前 MySQL 实例中所有的数据库列表2.2.2 创建数据库2.2.3 创建数据库表2.2.4 在表中插入数据2.2.5 在表中查询数据 三、服务器、数据库、表之间的关系四、SQL语句分类五、存储引擎 一、数据库介绍 …

sql入门基础-2

Dml语句 对数据的增删改查 关键字 Insert增 Update删 Delete改 添加数据 给指定字段添加数据 Insert into 表明 (字段名1&#xff0c;字段名2) values&#xff08;值1&#xff0c;值2&#xff09;; 给全部字段添加数据--(根据位置对应添加到字段下) Insert into 表名 values…

套接字通信(C/C++ 多线程)----基于线程池的并发服务器

&#xff08;一&#xff09;大家可以看我写的这三篇&#xff0c;了解一下&#xff1a; 基于linux下的高并发服务器开发&#xff08;第四章&#xff09;- 多线程实现并发服务器_呵呵哒(&#xffe3;▽&#xffe3;)"的博客-CSDN博客https://blog.csdn.net/weixin_4198701…

【JavaWeb】Javascript经典案例

Javascript经典案例 注意&#xff1a;该文章是参考b站<20个JS经典案例>进行学习的&#xff0c;没有CSS的组成。 在慢慢更新中…哈哈哈哈&#xff0c;太慢了 文章目录 1.支付定时器2.验证码生成及校验 1.支付定时器 代码实现&#xff1a; confirm.html <!DOCTYPE html…

2.04 商品搜索功能实现

根据关键字获取分类查询对应的分页商品信息&#xff0c;并可以价格和销量进行排序切换 步骤1&#xff1a;mapper.xml编写sql语句 <!-- k: 默认&#xff0c;代表默认排序&#xff0c;根据name--> <!-- c: 根据销量排序--> <!-- p: 根据价格排序--> <sel…

消息队列 - 数据库操作

这里写自定义目录标题 前言数据表的插入删除操作关于实现接口类的几个注意实现实现封装创建DataBaseManager 类另一种获取Bean对象的方式 对数据库进行单元测试 前言 上一篇博客, 我们将消息队列的实体类创建完毕了, 并且还写了一些关于数据库的操作, 接下来我们继续进行关于数…

Java throw和throws 关键字

在Java中&#xff0c;异常可以分为两种类型&#xff1a; 未检查的异常&#xff1a;它们不是在编译时而是在运行时被检查&#xff0c;例如&#xff1a;ArithmeticException&#xff0c;NullPointerException&#xff0c;ArrayIndexOutOfBoundsException&#xff0c;Error类下的异…

wordpress 学习贴

安装问题 我的使用环境为docker环境&#xff0c;php、nginx、mysql分别处于3个容器中&#xff0c; 提示异常&#xff0c;打开debug模式&#xff0c;会发现 No such file or directory Warning: mysqli_real_connect(): (HY000/2002): No such file or directory 这个其实问题其…

Linux操作系统3-项目部署

手动部署 步骤 1.在idea中将文件项目进行打包 2.自定义一个文件目录&#xff0c;上传到Linux 3.使用 java -jar jar包名就可以进行运行 注意,如果需要启动该项目&#xff0c;需要确定所需的端口是否打开 采用这种方式&#xff0c;程序运行的时候会出现霸屏&#xff0c;并且会…