Volatile概述

Wesley13
• 阅读 492

Volatile概念

volatile是一个特征修饰符(type specifier)。volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。——百度百科

所以呢它主要是两个作用:一个是线程可见(保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。),一个是防止指令重排序。要理解这些首先呢需要了解我们java的一个内存模型(Java Memory Model,JMM)

Java Memory Model

我们知道在java中,实例域、静态域和数组元素都存储在堆内存中,堆内存是线程共享,而其他的一些虚拟机栈等它们的的一些内容是线程独占不会有内存可见的问题也不受内存模型影响。Java线程之间的通信由Java内存模型控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。Java内存模型的抽象示意图如下:

Volatile概述

线程从主内存拿取到某以变量到自己本地内存进行操作,完毕之后再将新的值覆盖到主内存。之后再有其他线程拿到此变量得到一个新的值。通过这样的方式达到了一个不同线程之间的通讯,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。

线程可见

当一个线程修改了共享变量的值,其他线程能够立即得知这个修改,这样的方式来保证单次读写操作的同步性。

例子1:j的值会是多少呢?

// 线程A执行的代码k = 5;//线程B执行的代码int j = k;

答案是无法确定。因为即使线程A已经把k的值更新为5,但是这个操作是在线程A的本地内存中完成的,本地内存所更新的变量并不会立即同步回主内存,因此线程B从主内存中得到的变量k的值是不确定的。这就是可见性问题,线程A对变量k修改了之后,线程B没有立即看到线程A修改的值。

例子2: 新线程会打印出end么?

public class Test {        private static /*volatile*/ boolean flag = true        public static void main (String[] args) throws I interrupted Exception {                new Thread(()-> {                        while (flag) {                                //do sth                        }                System•out•println("end");                },name: "server") .start();                Thread.sleep( millis: 1GGG);                flag = false    }}

答案是不会,新线程的本地内存拿到的flag是true,它一直使用的就是true。即使主线程已经将flag更改并同步到了主内存。新线程的本地空间已经有了flag也不会再去主内存取了。这时使用volatitle关键字修饰该变量就可以保证变量更改进行立刻同步,并且其他地方使用该变量每次都要重新从主内存拿取。

通过两个例子大概可以知道的是volatile修饰的变量,变动会及时更新并且线程都会去主内存取而不是到本地

指令重排序

实际上就是在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。

Volatile概述

  1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

  2. 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

  3. 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

这里先用一个列子说明重排序的存在,看下面伪代码:

a=0,b=0,x=0,y=0    

//线程一a=1;x=b;

//线程二b=1;y=a;

假如不会发生重排序。那么执行过程中两个线程四条指令至少a=1一定在x=b之前,b=1一定在y=a之前。执行顺序可能出现六种情况

//情况一:(串)线程一执行完后线程二才开始a=1;x=b;b=1;y=a;//结果x=0,y=1//情况二:(串)线程一开始时线程二已执行完b=1;y=a;a=1;x=b;//结果x=1,y=0

//情况三:(并)两线程交叉执行a=1;b=1;x=b;y=a;//结果x=1,y=1//情况四:(并)两线程交叉执行b=1;a=1;y=a;x=b;//结果x=1,y=1//情况五:(并)线程一执行中途线程二开始并执行完a=1;b=1;y=a;x=b;//结果x=1,y=1//情况五:(并)线程二执行中途线程一开始并执行完b=1;a=1;x=b;y=a;//结果x=1,y=1

在不会被调整顺序的情况中结果无非三种(1,0)、(0,1)、(1,1)但实际结果会出现x=0,y=0。也就是说线程的指令是乱序的会进行调整。对于单线程来说调整是不会影响结果的只是提升了效率比如省略一加一减相互抵消的指令或者调整顺序,最后结果不影响。

/*下面这三组就不会发生指令重排因为改了顺序就会影响结果*/a=1;b=a;a=1;a=2;a=b;b=1;

在上面双线程的例子中无论是线程一的a=1,x=b还是线程二的b=1,y=a。在它们本线程中两条语句并不是依赖的,所以调换不影响结果所以会出现调换。但两个线程放一起变量是依赖的,最后因为重排导致结果不一致。所以在多线程中往往会出现问题所以需要禁止重排,使用volatile那么指令之间加入内存屏障指令就可以禁止重排。

本文分享自微信公众号 - IT那个小笔记(qq1839646816)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
Java并发(六):volatile的实现原理
synchronized是一个重量级的锁,volatile通常被比喻成轻量级的synchronizedvolatile是一个变量修饰符,只能用来修饰变量。volatile写:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。volatile读:当读一个volatile变量时,JMM会把该线程对应的
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
2年前
Java 多线程:volatile关键字
概念volatile也是多线程的解决方案之一。\\volatile能够保证可见性,但是不能保证原子性。\\它只能作用于变量,不能作用于方法。当一个变量被声明为volatile的时候,任何对该变量的读写都会绕过高速缓存,直接读取主内存的变量的值。如何理解直接读写主内存的值:回到多线程生成的原因(Java内存模型与
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这