百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

Java之volatile关键字

liebian365 2025-01-04 21:17 35 浏览 0 评论

Java的volatile关键字用于标记一个java变量“存储在主内存中”。更准确的说,被volatile标记的变量每次读取操作都将从计算机主内存(computer's main memory)读取,而不是从CPU缓存中读取,而且对volatile变量的每次写入都是写入到主内存,而不仅仅是写到CPU缓存。

实际上,从jdk5之后volatile关键字不仅仅保证了从内存中读写volatile变量。我会在下面对此进行介绍。

一:变量可见性问题

volatile关键字保证了同一变量跨线程更改的可见性问题。

在多线程应用程序中,线程对非volatile变量进行操作,出于性能考虑,每个线程在处理变量时,可以将他们从主内存中复制到CPU缓存中,如果你的计算机包含一个以上的CPU,每个线程可以在不同的CPU上运行。这意味着,每个线程可以将变量复制到不同CPU的CPU缓存上。如下图所示:

对于非volatile变量而言,不能保证Java虚拟机(JVM)何时将主内存中数据读取到CPU缓存中,或何时将CPU缓存中数据写入到主内存中。这就导致了几个问题。

假设一个场景,超过2个及其以上的线程访问一个声明包含计数器变量的共享对象:

public class ShareCounterObject{
 public int counter = 0;
}

假设仅线程1能修改counter变量并且每次递增+1,线程1和线程2都时时的访问counter变量。

如果counter变量不用volatile关键字声明,那么将不能保证counter变量每次更新的值从CPU缓存中写回主内存中。这意味着counter变量在CPU缓存中的值和主内存中不同。

如图所示:

因为一个线程更新没有立即回写到主内存,导致其他线程不能获取非volatile变量最新的值,这种被叫做“可见性”问题。一个线程更新了非volatile声明的共享变量对其他线程不可见

二:Java volatile保证可见性

volatile关键字目的就是解决可见性问题。声明counter是volatile变量,所有对counter的更改都会立即从CPU缓存写回到主内存中.同时,其他读取这个变量的操作也将是从主内存中读取。

声明volatile变量代码块儿:

 public class ShareCounterObject{
 public volatile int counter = 0;
}

这样的话,线程1对counter变量更改,线程2对这个变量读取(但是不修改这个变量),volatile修饰的变量counter完全能保证在线程2写counter变量时的可见性。

如果线程1和线程2都递增counter变量,那么仅仅将其声明volatile是不够的。

三:Full volatile Visibility Guarantee

实际上volatile变量可见性保证volatile声明变量的本身,与他操作相关的变量也会保证。

规则1.如果线程A写入一个volatile变量,线程B随后读取同一个变量,那么线程A在写入volatile变量之前的所有可见的变量,线程B读取volatile变量后也可以看到。

规则2.如果线程A读取一个volatile变量,那么线程A读取volatile变量之前看到的所有变量都将从主内存中读取。

public class MyObject{
 private int years;
 private int months;
 private volatile int days;
 
 public void update(int years ,int months ,int days){
 this.years = years;
 this.months = months;
 this.days = days;
 }
}

这个代码块中只有days是volatile变量。根据完整的可见性保证规则所示,一个线程执行update方法对这3个变量修改,最后修改volatile变量,因为在他前面的变量years,months对该线程是可见的,那么当days变量写回到主内存中时years,months变量也会被写入到主内存。

public class MyObject{
 private int years;
 private int months;
 private volatile int days;
 
 public void update(int years ,int months ,int days){
 this.years = years;
 this.months = months;
 this.days = days;
 }

 public int totalDays(){
 int total = this.days;
 total += months * 30;
 total += years * 365;
 return total;
 }
}

如上代码块儿所示,执行total方法时,首先读取days的值到total变量,在读取days变量值的时候,同时后面的months和years变量也是读入到主内存中。因此可以通过上面的读序列能够看到days,months,years的最新值。

四:指令重排序

在保证语义不变的情况下,JVM和CPU因为性能的原因可能会进行指令重排序。例如:

 int a = 1;
 int b = 2;
 a++;
 b++;

如上代码,a,b变量在逻辑上毫无关系,指令重排序后可能执行顺序就是:

 int a = 1;
 a++;
 int b = 2;
 b++;

指令重排序虽然很利于性能的优化,但是也会导致一些意外的问题,如上面的MyObject对象,如果对update方法进行重排序之后:

 public void update(int years ,int months ,int days){
 this.days = days;
 this.years = years;
 this.months = months;
 }

变成这样之后,修改days变量的时候months,years变量仍然写入主内存,但这次days新值写入主内存发生在months,years写入之前。因此months,years的新值对其他线程是不可见的。那么重新排序的语义就发生了变化。

Happens-Before规则就是Java推出的一个方案。后面我会针对这个在做一个详细介绍。

本文相关资料链接:http://tutorials.jenkov.com/java-concurrency/volatile.html

如有不对的地方请留言指正,互相学习!

相关推荐

精品博文嵌入式6410中蓝牙的使用

BluetoothUSB适配器拥有一个BluetoothCSR芯片组,并使用USB传输器来传输HCI数据分组。因此,LinuxUSB层、BlueZUSB传输器驱动程序以及B...

win10跟这台计算机连接的前一个usb设备工作不正常怎么办?

前几天小编闲来无事就跑到网站底下查看粉丝朋友给小编我留言询问的问题,还真的就给小编看到一个问题,那就是win10跟这台计算机连接的一个usb设备运行不正常怎么办,其实这个问题的解决方法时十分简单的,接...

制作成本上千元的键盘,厉害在哪?

这是稚晖君亲自写的开源资料!下方超长超详细教程预警!!全文导航:项目简介、项目原理说明、硬件说明、软件说明项目简介瀚文智能键盘是一把我为自己设计的——多功能、模块化机械键盘。键盘使用模块化设计。左侧的...

E-Marker芯片,USB数据线的“性能中枢”?

根据线缆行业的研究数据,在2019年搭载Type-C接口的设备出货量已达到20亿台,其中80%的笔记本电脑和台式电脑采用Type-C接口,50%的智能手机和平板电脑也使用Type-C接口。我们都知道,...

ZQWL-USBCANFD二次开发通讯协议V1.04

修订历史:1.功能介绍1.1型号说明本文档适用以下型号:  ZQWL-CAN(FD)系列产品,USB通讯采用CDC类实现,可以在PC机上虚拟出一个串口,串口参数N,8,1格式,波特率可以根据需要设置(...

win10系统无法识别usb设备怎么办(win10不能识别usb)

从驱动入手,那么win10系统无法识别usb设备怎么办呢?今天就为大家分享win10系统无法识别usb设备的解决方法。1、右键选择设备管理器,如图:  2、点击更新驱动程序,如图:  3、选择浏览...

微软七月Win8.1可选补丁有内涵,含大量修复

IT之家(www.ithome.com):微软七月Win8.1可选补丁有内涵,含大量修复昨日,微软如期为Win7、Win8.1发布7月份安全更新,累计为6枚安全补丁,分别修复总计29枚安全漏洞,其中2...

如何从零开始做一个 USB 键盘?(怎么制作usb)

分两种情况:1、做一个真正的USB键盘,这种设计基本上不涉及大量的软件编码。2、做一个模拟的USB键盘,实际上可以没有按键功能,这种的需要考虑大量的软件编码,实际上是一个单片机。第一种设计:买现成的U...

电脑识别U盘失败?5个实用小技巧,让你轻松搞定USB识别难题

电脑识别U盘失败?5个实用小技巧,让你轻松搞定USB识别难题注意:有些方法会清除USB设备里的数据,请谨慎操作,如果不想丢失数据,可以先连接到其他电脑,看能否将数据复制出来,或者用一些数据恢复软件去扫...

未知usb设备设备描述符请求失败怎么解决

出现未知daousb设备设备描述符请求失du败解决办zhi法如下:1、按下Windows+R打开【运行】;2、在版本运行的权限输入框中输入:services.msc按下回车键打开【服务】;2、在服务...

读《飘》47章20(飘每章概括)

AndAhwouldn'tleaveMissEllen'sgrandchildrenfornotrashystep-patobringup,never.Here,Ah...

英翻中 消失的过去 37(消失的英文怎么说?)

翻译(三十七):消失的过去/茱迪o皮考特VanishingActs/JodiPicoult”我能做什么?“直到听到了狄利亚轻柔的声音,我才意识到她已经在厨房里站了好一会儿了。当她说话的时候,...

RabbitMQ 延迟消息实战(rabbitmq如何保证消息不被重复消费)

现实生活中有一些场景需要延迟或在特定时间发送消息,例如智能热水器需要30分钟后打开,未支付的订单或发送短信、电子邮件和推送通知下午2:00开始的促销活动。RabbitMQ本身没有直接支持延迟...

Java对象拷贝原理剖析及最佳实践(java对象拷贝方法)

作者:宁海翔1前言对象拷贝,是我们在开发过程中,绕不开的过程,既存在于Po、Dto、Do、Vo各个表现层数据的转换,也存在于系统交互如序列化、反序列化。Java对象拷贝分为深拷贝和浅拷贝,目前常用的...

如何将 Qt 3D 渲染与 Qt Quick 2D 元素结合创建太阳系行星元素?

Qt组件推荐:QtitanRibbon:遵循MicrosoftRibbonUIParadigmforQt技术的RibbonUI组件,致力于为Windows、Linux和MacOSX提...

取消回复欢迎 发表评论: