java并发-内存模型与volatile

JMM的关键技术点都是围绕着多线程的原子性、可见性和有序性来建立的。因此,我们首先必须了解这些概念

1,原子性

原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰,比如,对于一个静态全局变量int i,两个线程同时对它赋值,线程A给他赋值1,线程B给它赋值为-1。那么不管这2个线程以何种方式、何种步调工作,i的值要么是1,要么是-1。线程A和线程B之间是没有干扰的。这就是原子性的一个特点,不可被中断

2,可见性(Visibility)

可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改显然,对于串行程序来说,可见性问题是不存在的。因为你在任何一个操作步骤中修改了某个变量,那么在后续的步骤中,读取这个变量的值,一定是修改后的新值。

3 有序性(Ordering)

有序性问题可能是三个问题中最难理解的了。对于一个线程的执行代码而言,我们总是习惯地认为代码的执行是从先往后,依次执行的。这么理解也不能说完全错误,因为就一个线程内而言,确实会表现成这样。但是,在并发时,程序的执行可能就会出现乱序。给人直观的感觉就是:写在前面的代码,会在后面执行。听起来有些不可思议,是吗?有序性问题的原因是因为程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致

                                                                                                                   以上概念均摘自《实战java高并发程序设计》

Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。

主内存和工作内存

Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。此处的变量与Java编程时所说的变量不一样,指包括了实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,后者是线程私有的,不会被共享。

Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存(可以与前面讲的处理器的高速缓存类比),线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成,线程、主内存和工作内存的交互关系如下图所示,和上图很类似。

虚拟机内存交互关系

注意:这里的主内存、工作内存与Java内存区域的Java堆、栈、方法区不是同一层次内存划分,这两者基本上没有关系。

内存交互操作

由上面的交互关系可知,关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作来完成:

  • lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
  • unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作,如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。Java内存模型只要求上述两个操作必须按顺序执行,而没有保证必须是连续执行。也就是read和load之间,store和write之间是可以插入其他指令的,如对主内存中的变量a、b进行访问时,可能的顺序是read a,read b,load b, load a。Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:

  • 不允许read和load、store和write操作之一单独出现
  • 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
  • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一时刻只允许一条线程对其进行lock操作,lock和unlock必须成对出现
  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
  • 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

关于volatile

Java 使用了一些特殊的操作或者关键字来申明、告诉虚拟机,在这个地方,要尤其注意,不能随意变动优化目标指令。关键字volatile 就是其中之一。当你用volatile 去申明一个变量时,就等于告诉了虚拟机,这个变量极有可能会被某些程序或者线程修改。为了确保这个变量被修改后,应用程序范围内的所有线程都能够“看到”这个改动,虚拟机就必须采用一些特殊的手段,保证这个变量的可见性等特点。

先来看一个使用场景,如下面一段代码在32位java虚拟机上的运行

public class Test {public static long t = 0;public static class ChangeT implements Runnable {private long to;public ChangeT(long to) {this.to = to;}@Overridepublic void run() {while (true) {Test.t = to;Thread.yield();}}}public static class ReadT implements Runnable {@Overridepublic void run() {while (true) {long temp = Test.t;if (temp != 111L && temp != -999L && temp != 333L && temp != -444L) {System.out.println(temp);}Thread.yield();}}}public static void main(String args[]) {new Thread(new ChangeT(111L)).start();new Thread(new ChangeT(-999L)).start();new Thread(new ChangeT(333L)).start();new Thread(new ChangeT(-444L)).start();new Thread(new ReadT()).start();}
}

可以看出由于long是64位在32位的虚拟机上通过多线程赋值,导致数据的读和写都不是原子性的,因而造成,打印出的数字部分是乱的

-4294966963
4294966297
-4294966963
-4294966963
4294966297
-4294967185
-4294966963
4294966297
...

但当使用 volatile修饰后就没有问题了

public volatile static long t = 0;

 

同时也需要注意volatile是无法完全保证一些复合操作的原子性,它并不能够代替锁。

package com.ishop.controller;public class Test {public volatile static int i = 0;public static void main(String[] args) throws InterruptedException {AddThread t1 = new AddThread();AddThread t2 = new AddThread();AddThread t3 = new AddThread();AddThread t4 = new AddThread();t1.start();t2.start();t3.start();t4.start();System.out.println("i="+i);}
}
class AddThread extends Thread {@Overridepublic void run() {for (int a = 0; a < 100000; a++){Test.i++;}}
}

如:这一段代码,输出i总是一个小于100000的值。

对于volatile的特点以及使用场景总结如下:

volatile变量具有2种特性:

  • 保证变量的可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入,这个新值对于其他线程来说是立即可见的。
  • 屏蔽指令重排序:指令重排序是编译器和处理器为了高效对程序进行优化的手段,下文有详细的分析。

volatile语义并不能保证变量的原子性。对任意单个volatile变量的读/写具有原子性,但类似于i++、i–这种复合操作不具有原子性,因为自增运算包括读取i的值、i值增加1、重新赋值3步操作,并不具备原子性。

由于volatile只能保证变量的可见性和屏蔽指令重排序,只有满足下面2条规则时,才能使用volatile来保证并发安全,否则就需要加锁(使用synchronized、lock或者java.util.concurrent中的Atomic原子类)来保证并发中的原子性。

  • 运算结果不存在数据依赖(重排序的数据依赖性),或者只有单一的线程修改变量的值(重排序的as-if-serial语义)
  • 变量不需要与其他的状态变量共同参与不变约束

因为需要在本地代码中插入许多内存屏蔽指令在屏蔽特定条件下的重排序,volatile变量的写操作与读操作相比慢一些,但是其性能开销比锁低很多。

 

 

 

 

 

参考:《实战java高并发程序设计》

文章部分内容引自:http://blog.csdn.net/u011080472/article/details/51337422

转载于:https://www.cnblogs.com/china2k/p/8005770.html

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

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

相关文章

Notepad++使用教程

Notepad 快捷键 大全CtrlC 复制CtrlX 剪切CtrlV 粘贴CtrlZ 撤消CtrlY 恢复CtrlA 全选CtrlF 键查找对话框启动CtrlH 查找/替换对话框CtrlD 复制并粘贴当行CtrlL 删除当前行CtrlT 当行向上移动一行F3 查找下一个ShiftF3 查找上一个CtrlShiftF 组合在文件中查找CtrlF3 查找&#x…

安卓手机刷软路由_华为路由AX3 Pro上手测评:用过最方便的路由器,没有之一...

都说"科技改变生活"&#xff0c;但我总觉着&#xff0c;现如今的人们似乎被数码产品"奴役"了。比如说&#xff1a;之前买过某品牌路由器&#xff0c;设置过程之繁琐&#xff0c;直接让当时是数码小白的我崩溃了。自打那之后&#xff0c;我选购数码产品的标…

ECharts.js学习(二)动态数据绑定

Echarts 数据绑定 简单的统计表已经可以生成&#xff0c;不过之前图标数据都是直接写在参数里面的&#xff0c;而实际使用中&#xff0c;我们的数据一般都是异步读取的。EChart.js对于数据异步读取这块提供了异步加载的方法。 绑定多组数据 很多时候需要展示的数据不单单是一组…

黑苹果2k屏_一加手机屏幕新特性:120Hz、2K+ OLED、10bit

在发布了旗下的首款概念手机Concept One后&#xff0c;再次有消息显示一加将在1月13日也就是今天举办“屏幕技术沟通会”。而在发布会正式到来前&#xff0c;一加CEO刘作虎则在微博表示&#xff0c;一加已经完成了120Hz屏幕的研发。沟通会上还会聊一聊一加90Hz 流体屏引领行业后…

Java哈利波特死亡圣器下_如果编程语言是《哈利波特》中的人物

如果编程语言是《哈利波特》中的人物&#xff0c;本文是作者的一些观点&#xff0c;如果你有不同的看法&#xff0c;欢迎在留言中告诉我们。C语言每个人都在说&#xff0c;C语言已经死了至少十年以上了。人们对它嗤之以鼻。当然它也曾不可一世&#xff0c;肆虐横行&#xff0c;…

序列每天从0开始_【算法打卡】分割数组为连续子序列

难度&#xff1a;中等题目&#xff1a;给你一个按升序排序的整数数组 num(可能包含重复数字)&#xff0c;请你将它们分割成一个或多个长度为 3 的子序列&#xff0c;其中每个子序列都由连续整数组成。如果可以完成上述分割&#xff0c;则返回 true &#xff1b;否则&#xff0c…

php会员系统源码_b2b2c商城系统会员预存款架构及源码分享

业务需求可以为预存款充值&#xff0c;在支付订单时使用预存款支付功能当预存款余额>商品订单总金额时&#xff0c;完全抵扣商品订单金额&#xff1b;当预存款余额架构一、 充值二、 数据结构1、会员钱包表(es_member_wallet)2、 后期可能会将会员积分等关于消费抵扣相关信息…

HTML5 规范

在学习编程的时候&#xff0c;每次看到那些整齐规范的代码&#xff0c;心里顿时对这个程序员表示点点好感&#xff0c;有时&#xff0c;比如看到自己和朋友写的代码时&#xff0c;那阅读起来就是苦不堪言&#xff0c;所以&#xff0c;一些基本的开发规范是必须的&#xff0c;是…

JWT.NET的使用

JWT.NET的使用 原文:JWT.NET的使用JWT是什么 JWT全称是Json Web Token&#xff0c;是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准&#xff08; RFC 7519 &#xff09;&#xff0c;定义了一种简洁的&#xff0c;自包含的方法用于通信…

正在搜索需要的文件_【数澜发布-澜寻】智能内容搜索引擎,释放“机器阅读” 潜力...

4月23日&#xff0c;又是一年世界读书日。知识传播的重要性在这一天被无数次提起&#xff0c;记录、阅读成为我们 “理解”这个世界的加速通道&#xff0c;但在科技力量迅猛发展的当下&#xff0c;阅读这件事早已不再局限于人类。人工智能应用到社会中很重要的一步就是模仿人类…

学习笔记(十一)——数据库的索引碎片、计划缓存、统计信息

1.索引碎片 数据库存储本身是无序的&#xff0c;建立了聚集索引&#xff0c;会按照聚集索引物理顺序存入硬盘。既键值的逻辑顺序决定了表中相应行的物理顺序 而且在大多数的情况下&#xff0c;数据库写入频率远低于读取频率&#xff0c;索引的存在为了读取速度牺牲写入速度&…

网络软文发布软件_星浪中合网软文写出爆款汽车营销软文的特点是什么?

在移动互联网快速发展的时代&#xff0c;人们都是使用移动设备来获取外界信息。过去&#xff0c;许多广告汽车公司都专注于线下广告&#xff0c;但是现在他们已经将广告费用投入了移动互联网市场。移动互联网营销方法很多&#xff0c;例如&#xff0c;软文营销是一种非常强大的…

通达oa wbupload.php,关于通达OA上传附件类型限制的设置

关于上传附件类型限制的设置有些用户在使用OA系统的过程中&#xff0c;要求只允许上传比如后缀名为doc&#xff0c;xls&#xff0c;ppt等的附件文件。如何设置才能实现这一点呢&#xff1f;下面简单介绍下设置方法。1、在该路径MYOA\webroot\inc下找到配置文件oa_config.php文件…

ADC应用

数模转换&#xff08;ADC&#xff09;的应用笔记 智能时代&#xff0c;数字信号已体现在我们生活的方方面面&#xff0c;A/D,D/A是重要的基础。智能手机触摸信号需要转换为数字信号才能分辨触摸位置、数字去抖&#xff1b;打电话或者麦克风需要将模拟声信号转换为数字信号以便存…

springboot 禁用tomcat_Spring Boot 面试的十个问题

点击左上角蓝字&#xff0c;关注“SpringForAll社区”专注分享Spring周边技术内容用下面这些常见的面试问题为下一次 Spring Boot 面试做准备。在本文中&#xff0c;我们将讨论 Spring boot 中最常见的10个面试问题。现在&#xff0c;在就业市场上&#xff0c;这些问题有点棘手…

go 怎么等待所有的协程完成_理解真实世界中 Go 的并发 BUG

点击上方蓝色“Go语言中文网”关注&#xff0c;回复「电子书」领全套Go资料有几个学生研究归纳了go编程中的并发bugs&#xff0c;发表了一篇(英文)论文&#xff1a;《Understanding Real-World Concurrency Bugs in Go》。为你下载好了 PDF&#xff0c;关注公众号 Go语言中文网…

java地图图表动态亮点,可视化图表行动指南:地表最强解读来了

原标题&#xff1a;可视化图表行动指南&#xff1a;地表最强解读来了身处信息技术高速发展的时代&#xff0c;数据价值日益凸显&#xff0c;然而如何将数据更好的展示&#xff0c;让别人一看就懂且眼前一亮可是一门大学问。此前&#xff0c;小亿在广大数友的强烈要求下结合前人…

ASP.NET MVC 使用Swagger需要注意的问题!!!

之前&#xff0c;一直使用的微软自带的Microsoft.AspNet.WebApi.HelpPage来作为项目的接口文档&#xff0c;但总感觉有些不足&#xff0c;就准备采用Swagger。 在项目中引用Swagger很方便&#xff0c;直接Nuget搜索安装Swashbuckle就可以。但是&#xff0c;在使用的过程中&…

分块试水--CODEVS4927 线段树练习5

模板 1 #include<stdio.h>2 #include<algorithm>3 #include<string.h>4 #include<stdlib.h>5 #include<math.h>6 //#include<bitset>7 //#include<iostream>8 using namespace std;9 10 int n,m,q;11 #define maxn 10001112 #define…

【JVM】类的生命周期【转+整理】

参考如下三篇并整理。 1.Java类加载机制详解 2.深入理解Java&#xff1a;类加载机制及反射 3.jvm系列(一):java类的加载机制 类的生命周期是从被加载到虚拟机内存中开始&#xff0c;到卸载出内存结束。过程共有七个阶段。 1.加载---2.验证---3.准备---3.解析---5.初始化---6.使…