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

news2024/11/14 11:08:19

说说对于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 static volatile 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/819002.html

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

相关文章

心理测量平台目录遍历

你知道&#xff0c;幸福不仅仅是吃饱穿暖&#xff0c;而是勇敢的战胜困难。 漏洞描述 心理测量平台存在目录遍历漏洞&#xff0c;攻击者可利用该漏洞获取敏感信息。 漏洞复现 访问目录遍历漏洞路径&#xff1a; /admin/漏洞证明&#xff1a; 文笔生疏&#xff0c;措辞浅薄…

解决AttributeError: ‘DataParallel‘ object has no attribute ‘xxxx‘

问题描述 训练模型时&#xff0c;分阶段训练&#xff0c;第二阶段加载第一阶段训练好的模型的参数&#xff0c;接着训练 第一阶段训练&#xff0c;含有代码 if (train_on_gpu):if torch.cuda.device_count() > 1:net nn.DataParallel(net)net net.to(device)第二阶段训练…

苍穹外卖黑马1

苍穹外卖项目&#xff08;12天&#xff09;分布如下&#xff1a; 第一章&#xff1a;环境搭建&#xff08;1天&#xff09; day01&#xff1a;项目概述、环境搭建 第二章&#xff1a;基础数据维护&#xff08;3天&#xff09; day02&#xff1a;员工管理、分类管理 day03: 菜品…

React哲学——官方示例

在本篇技术博客中&#xff0c;我们将介绍React官方示例&#xff1a;React哲学。我们将深入探讨这个示例中使用的组件化、状态管理和数据流等核心概念。让我们一起开始吧&#xff01; 项目概览 React是一个流行的JavaScript库&#xff0c;用于构建用户界面。React的设计理念是…

C++多态(2) ——抽象类与final、override关键字

目录 一.抽象类 1.定义 2.形式 3.举例&#xff1a; 解决方法&#xff1a;让子类重写纯虚函数&#xff0c;重写后子类就会变换为具体类&#xff0c;能够创建出对象了。 3.抽象类的作用 二.final与override关键字 方法1&#xff1a;私有父类构造函数 方法2&#xff1a;私有…

Linux - 进程控制(创建和终止)

1.进程创建 fork函数初识 在linux中fork函数时非常重要的函数&#xff0c;它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。 返回值&#xff1a;子进程返回0&#xff0c;父进程返回子进程id&#xff0c;出错返回-1 getpid()获取子进程id&#xf…

JVM、Redis、反射

JVM JVM是Java virtual machine&#xff08;Java虚拟机&#xff09;的缩写&#xff0c;是一种用于计算机的规范&#xff0c;是通过在实际计算机上仿真模拟各种计算机功能来实现的。 主要组件构成&#xff1a; 1.类加载器 子系统负责从文件系统或者网络中加载Class文件&…

分布式文件存储与数据缓存 Redis高可用分布式实践(下)

六、Redisweb实践 网页缓存 1.创建springboot项目 2.选择组件 Lombok spring mvc spring data redis spring data jpa 3.编写配置文件 ### 数据库访问配置 spring.datasource.driver-class-namecom.mysql.jdbc.Driver spring.datasource.urljdbc:mysql://192.168.66.100:3307/…

uniapp scroll-view显示滚动条

在style中添加样式&#xff1a; ::v-deep ::-webkit-scrollbar {/* 滚动条整体样式 */display: block;width: 10rpx !important;height: 10rpx !important;-webkit-appearance: auto !important;background: transparent;overflow: auto !important;}::v-deep ::-webkit-scroll…

matlab使用教程(6)—线性方程组的求解

进行科学计算时&#xff0c;最重要的一个问题是对联立线性方程组求解。在矩阵表示法中&#xff0c;常见问题采用以下形式&#xff1a;给定两个矩阵 A 和 b&#xff0c;是否存在一个唯一矩阵 x 使 Ax b 或 xA b&#xff1f; 考虑一维示例具有指导意义。例如&#xff0c;方程 …

测试|自动化测试(了解)

测试|自动化测试&#xff08;了解&#xff09; 1.什么是自动化测试☆☆☆☆ 自动化测试相当于把人工测试手段进行转换&#xff0c;让代码执行。 2.自动化测试的分类☆☆☆☆ 注&#xff1a;这里只是常见的自动化测试&#xff0c;并不全部罗列。 1.单元自动化测试 其中Java…

分布式开源监控Zabbix实战

Zabbix作为一个分布式开源监控软件&#xff0c;在传统的监控领域有着先天的优势&#xff0c;具备灵活的数据采集、自定义的告警策略、丰富的图表展示以及高可用性和扩展性。本文简要介绍Zabbix的特性、整体架构和工作流程&#xff0c;以及安装部署的过程&#xff0c;并结合实战…

数据结构 | Radix Tree 树

什么是基数树&#xff1f; 基数树是一种多叉搜索树&#xff0c;数据位于叶子节点上&#xff0c;每一个节点有固定的2^n个子节点&#xff08;n为划分的基大小&#xff0c;当n为1时&#xff0c;为二叉树&#xff09;。 什么为划分的基&#xff1f; 以一个64位的长整型为例&#x…

oracle 19c打补丁遭遇OPATCHAUTO-72043OPATCHAUTO-68061

最近&#xff0c;在AIX上的新装oracle 19C数据库基础版本&#xff0c;使用opatchauto打PSU补丁集35037840时遇到了OPATCHAUTO-72043报错&#xff0c;无法正常应用GI补丁。 一、环境描述 操作系统&#xff1a;AIX 数据库版本&#xff1a;oracle rac 19.3.0新装基础版 应用PS…

代码随想录第四十八天|198、213、337.打家劫舍

198.打家劫舍 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系统会自动报警。 给定一个代表每个…

【JAVASE】重载与递归

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;浅谈java &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; 重载与递归 1. 方法重载1.1 为什么需要方…

开源代码分享(9)—面向100%清洁能源的发输电系统扩展规划(附matlab代码)

1.背景介绍 1.1摘要 本文提出了一种新颖的建模框架和基于分解的解决策略&#xff0c;将随机规划&#xff08;SP&#xff09;和鲁棒优化&#xff08;RO&#xff09;相结合&#xff0c;以应对协调中长期电力系统规划中的多重不确定性。从独立系统运营商&#xff08;ISO&#xff…

CAD曲面建模

首先还是要在3d绘图的环境中进行 在网络菜单栏的左侧&#xff0c;有曲面建模相关的功能 包括旋转形成曲面、平移形成曲面&#xff0c;按照两条线形成网格&#xff0c;按照四条封闭的线形成曲面等等 首先演示旋转曲面 需要被旋转的对象&#xff0c;以及旋转轴 首先选择要旋转…

Leetcode | DP | 338. 198. 139.

338. Counting Bits 重点在于这张图。 从i1开始&#xff0c;dp的array如果i是2的1次方之前的数&#xff0c;是1 dp[i - 2 ^ 0]; 如果i是2的2次方之前的数&#xff0c;是1 dp[i - 2 ^ 1]; 如果i是2的3次方之前的数&#xff0c;是1 dp[i - 2 ^ 2]; 198. House Robber 如果…

Hadoop学习指南:探索大数据时代的重要组成——Hadoop运行模式(下)

Hadoop运行模式(下&#xff09; 前言2.6 配置历史服务器1&#xff09;配置mapred-site.xml2&#xff09;分发配置3&#xff09;在hadoop102启动历史服务器4&#xff09;查看历史服务器是否启动5&#xff09;查看JobHistory 2.7 配置日志的聚集1&#xff09;配置yarn-site.xml2&…