【Java基础】volatile关键字

news2024/11/23 3:50:25

关于作者:CSDN内容合伙人、技术专家, 从零开始做过日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。

目录

  • 一、导读
  • 二、概览
    • 2.1 作用
    • 2.2 多线程共享变量的访问流程
    • 2.3 多线程为什么会出现可见性问题
    • 2.4 volatile如何实现可见性
    • 2.5 如何实现禁止指令重排序
    • 2.6 举例
    • 2.7 来答题
  • 三、原理
    • 3.2 使用场景:
  • 四、 推荐阅读

一、导读

我们继续总结学习Java基础知识,温故知新。

二、概览

volatile 是一个Java关键字,可以用来修饰变量,volatile也被称为轻量级的synchronized,运行时开销比 synchronized更小。

2.1 作用

1、确保共享变量在线程之间的同步,实现可见性。
2、禁止处理器重排序。

2.2 多线程共享变量的访问流程

线程执行时,先拷贝主存数据到本线程本地,操作完成后再把结果从线程本地刷到主存。
我们看下面的图,多线程时,共享变量操作完的值是在红色的区域。
在这里插入图片描述

2.3 多线程为什么会出现可见性问题

可见性是由于CPU缓存引起,CPU 增加了缓存,以均衡与内存的速度差异,导致 可见性问题。
在多线程环境下,多个线程同时访问共享变量,由于不同线程的执行顺序和时间不确定,可能会导致一个线程对共享变量的修改在其他线程中不可见。可参考上面共享变量的访问流程。

2.4 volatile如何实现可见性

volatile不允许线程内进行缓存和重排序,直接修改内存,所以对其他线程是可见的。

被volatile修饰的变量读写时,都会直接刷到主存,从而使得变量可见。

2.5 如何实现禁止指令重排序

volatile是通过编译器在生成字节码时,在指令序列中添加“内存屏障”来禁止指令重排序的。

当一个变量被修饰时,表示变量是“易变的”(volatile)或者“不稳定的”(unstable),它意味着该变量的值可能会被其他线程(或进程)修改。

注意: volatile 关键字只能保证线程之间的同步,不能保证线程安全
要保证线程安全,需要使用其他同步机制,比如 synchronized 关键字或者 Lock 接口。

volatile具有可见性、有序性,但不具有原子性,所以是线程不安全的。

2.6 举例

  • 不使用volatile关键字
禁止线程缓存变量结果。
可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。
引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。
举例:
// Thread-A
new Thread("Thread A") {
    @Override
    public void run() {
        while (!stop) {
        }
        System.out.println(Thread.currentThread() + " stopped");
    }
}.start();

一个线程内使用了停止的开关,假如这个stop没有被volatile修饰,我们在线程b中修改,
线程a并不知道开关的值被修改了。
  • 使用volatile 防重排序
从一个最经典的例子来分析重排序问题。大家应该都很熟悉单例模式的实现
public class Singleton {
public static volatile Singleton singleton;
    /**
    * 构造函数私有,禁止外部实例化
    */
    private Singleton() {};
    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

实例化一个对象其实可以分为三个步骤:
* 分配内存空间。
* 初始化对象。
* 将内存空间的地址赋值给对应的引用。
但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:
* 分配内存空间。
* 将内存空间的地址赋值给对应的引用。
* 初始化对象

如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。
因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。
  • 使用volatile 保证原子性:单次读/写
volatile变量的单次读/写操作可以保证原子性的,如longdouble类型变量,
但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作,要保证多步的原子性,
可以通过AtomicInteger或者Synchronized来实现,本质上就是cas操作。

2.7 来答题

  • 问题1: i++为什么不能保证原子性?
i++其实是一个复合操作,包括三步骤:
* 读取i的值。
* 对i加1* 将i的值写回内存。 
volatile是无法保证这三个操作是具有原子性的,
我们可以通过AtomicInteger或者Synchronized来保证+1操作的原子性。 
  • 问题2: 共享的long和double变量的为什么要用volatile?
因为longdouble两种数据类型的操作可分为高32位和低32位两部分,
因此普通的longdouble类型读/写可能不是原子的。
因此,鼓励大家将共享的longdouble变量设置为volatile类型,
这样能保证任何情况下对longdouble的单次读/写操作都具有原子性

三、原理

在JVM底层volatile是采用“内存屏障”来实现的,加入volatile关键字时,会多出一个lock前缀指令
内存屏障,又称内存栅栏,是一个 CPU 指令。

1、用javac命令进行编译生成.class文件,
2、再用javap命令反编译查看.class文件的信息,就可以看到字节码信息中多了一些指令。

为了保证各个处理器的缓存是一致的,实现了缓存一致性协议(MESI),每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

volatile 变量通过这样的机制就使得每个线程都能获得该变量的最新值。

3.2 使用场景:

解决对变量可见性有要求,但是对读取顺序没要求的需求。

  • volatile特性:
    (1)volatile仅能使用在变量级别
    (2)volatile仅能实现变量的修改可见性,不能保证原子性,volatile + cas 就实现了原子性,如atomic包下面的类。
    (3)volatile不会造成线程的阻塞
    (4)volatile标记的变量不会被编译器优化

四、 推荐阅读

【Java基础】原子性、可见性、有序性

【Java基础】java可见性之 Happens-before

【Java基础】java-android面试Synchronized

【Java基础】java-android面试-线程状态

【Java基础】线程相关

【Java基础】java 异常

【Java基础】java 反射

【Java基础】java 泛型

【Java基础】java注解

【Java基础】java动态代理

【Java基础】Java SPI

【Java基础】Java SPI 二 之 Java APT

【Java基础】 jvm 堆、栈、方法区 & java 内存模型

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

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

相关文章

EMC学习笔记(十二)跨分割区及开槽的处理

跨分割区及开槽的处理 1.开槽的产生1.1 对电源/地平面分割造成的开槽1.2 通孔过于密集形成开槽 2.开槽对PCB板EMC性能的影响2.1 高速信号与低速信号的面电流分布2.2 分地的概念2.3 信号跨越电源平面或地平面上的开槽的问题 3.对开槽的处理3.1 需要严格的阻抗控制的高速信号线&a…

基于Hadoop的豆瓣电影的数据抓取、数据清洗、大数据分析(hdfs、flume、hive、mysql等)、大屏可视化

目录 项目介绍研究背景国内外研究现状分析研究目的研究意义研究总体设计数据获取网络爬虫介绍豆瓣电影数据的采集 数据预处理数据导入及环境配置Flume介绍Hive介绍MySQL介绍Pyecharts介绍环境配置及数据加载 大数据分析及可视化豆瓣影评结构化分析豆瓣电影类型占比分析豆瓣电影…

Vector - CAPL - 常见缩写词

我们在CAPL脚本开发过程中,为了表示的方便,我们会使用大量的缩写词,为了方便自己的使用,大家也可作为参考使用,这里就整理出来一部分常用缩写,以及中英文注释。 CAPL开发中常见缩写 缩写英文解释中文ABSA…

数据安全系列(二)丨数据分类分级

1 数据分类分级的依据 2016年11月,《网络安全法》明确将“数据分类”作为网络安全保护法定义务之一。 2021年9月,《数据安全法》再次具体确立了“数据分类分级保护制度”及其基本原则。 《数据安全法》 第二十一条 国家建立数据分类分级保护制度&am…

Ansible与Shell结合使用

利用Shell脚本运行Ansible命令: 本实验采用rhel8.3。这次将使用 yum_repository 模块远程写入仓库配置。 写入仓库: Shell脚本: #!/bin/bashansible dev -m yum_repository -a namemyBase description"myTestRepo \ baseurl"/m…

物联网的未来:连接万物的智能世界

第一章:引言 在当今数字化时代,物联网(Internet of Things,IoT)已经成为了人们生活中不可或缺的一部分。物联网技术的快速发展和广泛应用,将为我们带来一个连接万物的智能世界。本文将探讨物联网的未来发展…

内核态与用户态详解(嵌入式学习)

内核态与用户态 内核态概念特点和功能 用户态概念特点和功能 内核态与用户态如何切换?总结 内核态 概念 内核态(Kernel Mode)是计算机操作系统中的一种特权级别或运行模式。在内核态下,操作系统拥有最高的权限和访问系统资源的能…

vue-tsc --noEmit导致打包报TS类型错误

🐱 个人主页:不叫猫先生,公众号:前端舵手 🙋‍♂️ 作者简介:2022年度博客之星前端领域TOP 2,前端领域优质作者、阿里云专家博主,专注于前端各领域技术,共同学习共同进步…

使用预计算的纹理替换Hololens 2屏幕的内容

需求:自己渲染器生成的纹理是A,Unity里的virtual camera生成的纹理是B,正常情况下眼镜里看到的是B,我想着直接用A替换掉B。 了解Unity渲染脚本的生命周期: 对于我们的需求,关键的是Scene Rendering这一块内…

基于ChatGLM2和langchain的本地知识库问答的实战方案

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

【C语言】计算机是如何存储整数和小数的?

文章目录 前言一、正整数和0二、负整数三、小数1、定点数(1)什么是定点数(2)表示精度(3)表示范围(4)优缺点 2、浮点数(1)什么是浮点数(2&#xff…

Linux——搭建jdk环境

标题Linux——搭建jdk环境 Linux搭建jdk,案例采用jdk8 1.检查linux位数(64和32位) [rootnode1 /]# getconf LONG_BIT 642.下载对应的linux JDK包,这里使用jdk8 下载jdk8 : 链接: https://www.oracle.com/java/technologies/downloads/#java8 3、上…

苹果又撕下了国产手机遮羞布,国内消费者最爱iPhone

618结束之后,各个国产手机品牌都说自己表现优秀,比去年增长了多少多少,不过随着分析机构给出具体的数据,国产手机可谓被撕下了遮羞布,国内消费者最爱的还是iPhone。 分析机构指出618期间iPhone的销量高达562万部&#…

【互斥锁与信号量】线程通信:互斥锁(mutex)与信号量(semaphore)

目录 0. 互斥锁与信号量 同步互斥概述 1. 互斥锁 1.1 互斥锁的概念 1.2 互斥锁初始化:pthread_mutex_init函数 1.3 互斥锁上锁:pthread_mutex_lock函数 1.4 互斥锁解锁:pthread_mutex_unlock函数 1.5 销毁互斥锁:pthread_…

chatgpt赋能python:Python重写父类方法:在OOP编程中的应用

Python重写父类方法:在OOP编程中的应用 在Python的面向对象编程范式中,继承是一种非常重要的概念。当我们声明一个类时,我们可以通过继承来扩展类的功能并避免重复编写代码。在这个过程中,很可能你会碰到需要重写父类方法的情况。…

17.RocketMQ之死信队列

highlight: arduino-light 1.5 死信队列 当一条消息初次消费失败,消息队列 RocketMQ 会自动进行消息重试;达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息,此时,消息队列…

flutter 简介 flutter 能为我们做什么

flutter 简介 flutter 能为我们做什么 前言一、什么是Flutter?二、Flutter的特点和优势三、Flutter与其他跨平台框架的比较总结 前言 陆陆续续已经写了60多篇的flutter 的文章了,本篇文章就来说说我对flutter 的简单看法 一、什么是Flutter&#xff1f…

【Rust】安装

文章目录 1.官网下载2.安装3.安装验证4.打开本地文档5.安装插件6.HelloWorld①新建项目目录使用VSCode打开②新建rs文件③编译④运行 7.HelloCargo①新建项目目录使用VSCode打开②cargo build③cargo run④cargo check⑤为发布构建 8.更新与卸载 1.官网下载 官网地址&#xff…

c++11 标准模板(STL)(std::basic_ostream)(一)

定义于头文件 <ostream> template< class CharT, class Traits std::char_traits<CharT> > class basic_ostream : virtual public std::basic_ios<CharT, Traits> 类模板 basic_ostream 提供字符流上的高层输出操作。受支持操作包含有格式…

工业读码器在工业生产上应用的优势有哪些?

工业读码器是一种用于读取和解码条形码、二维码等信息的设备&#xff0c;一般广泛应用于工业生产中。可以辅助企业进行工业生产流程、物料等方面的管理。下面我们就一起来了解一下&#xff0c;工业读码器在工业生产上应用的优势有哪些&#xff1f; 工业读码器在工业生产上应用…