IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 深入理解volatile(Java) -> 正文阅读

[Java知识库]深入理解volatile(Java)

作者:recommend-item-box type_blog clearfix

前言

除了上篇文章讲到的关键字synchronize关键字外可以实现同步外,java中还有另一个关键字volatile可以实现一些简单的同步。
synchronized知识的了解可查看深入理解synchronized(一)——初识synchronized

定义

volatile是一个特征修饰符.volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。其在许多语言中都有应用,在java中是一个关键字,其作用主要有以下两点:

  1. 保证此变量对所有的线程的可见性。即一个线程对volatile修改后,其他线程能够立马感知到此变量的新值。
  2. 禁止指令重排序优化。指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理,volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障。

下面我们谈谈volatile实现上述功能的原理,为了更好的理解,我们先讲解volatile如何让禁止指令重排序。

volatile与指令重排序

首先我们了解下指令重排序的概念。

指令重排序

大家有没有想过,我们编写的java代码,机器在执行的时候一定按我们编写的顺序一条一条执行吗?答案是否定的,因为我们编写的代码顺序很可能不是机器cpu执行较优的顺序,因此java内存模型允许编译器和处理器对在编译器或运行时对指令重排序以提高性能,并且只会对不存在的数据依懒性的指令进行重排序。

虽然指令排序能提高性能,但不代表所有的语句都会进行指令重排序,上面也说了指令间需要不存在数据依赖性,数据依赖性有以下3种类型:

| 类型 |代码举例 |说明|
| — | — | | — ||
| 写后读| a=1;b=a |写一个变量后,在读这个变量|
| 写后写| a=1;a=2 |写一个变量后,又改变(写)这个变量的值|
| 读后写| b=a;a=2 |读一个变量后,又改变(写)这个变量的值|
对于上面这三种类型,如果改变代码的执行顺序,很明显执行结果不符合预期。所以,编译器和处理器在重排顺序的时候,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。也就是说:在单线程环境下,指令执行的最终效果应当与器在顺序执行下的效果一致,否则这种优化便会失去意义。这句话有个专业术语叫做as-if-serial语义(有兴趣的同学可查略资料进一步了解)。

听起来指令重排序能提高性能,岂不妙哉,但与此同时也会带来一些问题,很容易想到在多线程情况下,会出现由于指令重排序导致一些非预期结果的出现。
而被volatile修饰的变量,在汇编层指令会对volatile变量操作的指令加一个lock前缀的汇编指令。若变量被修改后,会立刻将变量由工作内存回写到主存中。那么意味了之前的操作已经执行完毕。这就是内存屏障。

可见性

我们首先看看下面的例子来理解变量对所有线程的可见性,我们定义了一个类变量num初始值为0,使用addNum()对num加1,当我们开启N个线程(n>2)去执行addNum(),发现结果num的值并不一定等于N,当调大N时可以明显感知到结果很可能小于N。

/**
 * 测试指令重排序
 */
public class ReadThread {

    private static int num = 0;

    public static void addNum() {
        num++;
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10000; i++) {
            Thread addThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    addNum();
                }
            });
            addThread.start();
        }
        System.out.println(num);

    }

}

运行结果不等于N的原因,是因为num++并不是一个原子操作,反编译后可以看到i++的执行指令如下:

image.png
各指令含义如下:

  • getstatic:获取指定类的静态域, 并将其压入栈顶
  • iconst_1:将int型1推送至栈顶
  • iadd:将栈顶两int型数值相加并将结果压入栈顶
  • putstatic:为指定类的静态域赋值
  • return:从当前方法返回void

通过上面指令解释大概可以猜测问题出现指令getstatic上,线程获取num值时,没有获取到num被其他线程修改后的最新值,导致最终结果不一致,这里面原因与java内存模型JMM有关,JMM中规定所有的变量都存储在主内存(Main Memory)中,每条线程都有自己的工作内存(Work Memory),线程的工作内存中保存了该线程所使用的变量的从主内存中拷贝的副本。线程对于变量的读、写都必须在工作内存中进行,而不能直接读、写主内存中的变量。同时,本线程的工作内存的变量也无法被其他线程直接访问,必须通过主内存完成。

image.png
对于普通共享变量,线程A将变量修改后,体现在此线程的工作内存。在尚未同步到主内存时,若线程B使用此变量,从主内存中获取到的是修改前的值,便发生了共享变量值的不一致,也就是出现了线程的可见性问题,这也是上述代码结果不符合预期的原因。

而当我们给变量num加上volatile后,便可以解决此问题,这是因为当对volatile变量执行写操作后,JMM会把工作内存中的最新变量值强制刷新到主内存,并且写操作会导致其他线程中的缓存无效。使得其他线程从主存中获取到volatile变量的最新值。

volatile为什么没有原子性

volatile保证了读写一致性。但是当线程2已经使用旧值完成了运算指令,且将要回写到内存时,是不能保证原子性的。

小例子:日常开发使用git或svn开发项目时存在主干和分支,有一个全项目都使用的枚举类,当小红修改了该类立即提交主干,并通知其他小伙伴:“你们使用这个类时需要在主干上拉取一下”,但是此时小明在旧版本开发完毕并且正在提交这个类,导致了冲突。

volatile读写性能

volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

结语

本文介绍了volatile的作用,主要有可见性与禁止指令重排两大作用,以及其具备可见性的原因,至于如何实现禁止指令重排序,未作详细介绍,涉及到Java代码、字节码、Jdk源码、汇编层面、硬件层面等底层实现,有兴趣可自行查阅资料了解。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-14 23:36:51  更:2022-04-14 23:40:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:30:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码