内存位置访问无效 midas.dll_java并发之内存模型

作者:killianxu

来源:https://www.cnblogs.com/killianxu/p/11665903.html

e1ef38bf60b48991a61d27838289f572.png

java内存模型知识导图

一 并发问题及含义

并发编程存在原子性、可见性、有序性问题。

  1. 原子性即一系列操作要么都执行,要么都不执行。
  2. 可见性,一个线程对共享变量的修改,另一个线程可能不会马上看到。由于多核CPU,每个CPU核都有高速缓存,会缓存共享变量,某个线程对共享变量的修改会改变高速缓存中的值,但却不会马上写入内存。另一个线程读到的是另一个核缓存的共享变量的值,出现缓存不一致问题。
  3. 有序性,即程序执行的顺序按照代码的先后顺序执行。编译器和处理器会对指令进行重排,以优化指令执行性能,重排不会改变单线程执行结果,但在多线程中可能会引起各种各样的问题。

二 内存模型

为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。内存模型解决并发问题

主要采用两种方式:限制处理器优化和使用内存屏障。

顺序一致性内存模型是一种理论参考模型,提供了极强的内存可见性保证,具有两大特性:

  1. 一个线程的所有操作按照程序的顺序执行,而不能重排序。
  2. 所有线程只能看到单一的执行顺序。每个操作都必须原子执行且立刻对其它线程可见。

顺序一致性内存模型禁止很多处理器和编译器重排,影响执行性能,处理器内存模型和JMM对顺序一致性内存模型进行放松,执行性能:处理器内存模型>JMM>顺序一致性内存模型,易编程性:处理器内存模型

三 java内存模型

Java内存模型(Java Memory Model ,JMM)是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。

Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。主内存和工作内存可类比成计算机内存模型中的主存和缓存的概念。

3.1 java内存模型解决并发问题方法

原子性,在java中,只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。在32位平台下,对64位数据的赋值是需要通过两个操作来完成,不能保证其原子性。要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock保证任一时刻只有一个线程执行该代码块,从而保证了原子性。

可见性,Java提供了volatile关键字来保证可见性,当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

JMM通过happens-before关系向程序员提供跨线程的内存可见性保证:

  1. 程序次序规则:一段代码在单线程中执行的结果是有序的。注意是执行结果,因为虚拟机、处理器会对指令进行重排序(重排序后面会详细介绍)。虽然重排序了,但是并不会影响程序的执行结果,所以程序最终执行的结果与顺序执行的结果是一致的。故而这个规则只对单线程有效,在多线程环境下无法保证正确性。
  2. 锁定规则:这个规则比较好理解,无论是在单线程环境还是多线程环境,一个锁处于被锁定状态,那么必须先执行unlock操作后面才能进行lock操作。
  3. volatile变量规则:这是一条比较重要的规则,它标志着volatile保证了线程可见性。通俗点讲就是如果一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作一定是happens-before读操作的。
  4. 传递规则:提现了happens-before原则具有传递性,即A happens-before B , B happens-before C,那么A happens-before C
  5. 线程启动规则:假定线程A在执行过程中,通过执行ThreadB.start()来启动线程B,那么线程A对共享变量的修改在接下来线程B开始执行后确保对线程B可见。
  6. 线程终结规则:假定线程A在执行的过程中,通过制定ThreadB.join()等待线程B终止,那么线程B在终止之前对共享变量的修改在线程A等待返回后可见。

有序性,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别:volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。

3.2 java并发原语

Java内存模型,除了定义了一套规范,还提供了一系列原语,封装了底层实现后,供开发者直接使用。

3.2.1 volatile

内存语义:

当写一个volatile变量时,JMM会把该线程对应的本地内存中的所有共享变量刷新到主内存。

当读一个volatile变量,JMM会把该线程对应的本地内存置为无效,线程接下来从主内存中读取共享变量。

实现:

编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

在每个volatile写操作前面插入一个StoreStore屏障。StoreStore屏障禁止上面的普通写和volatile写重排序,保障上面的普通写在volatile写之前刷新到主内存。

在每个volatile写操作后面插入一个StoreLoad屏障。避免volatile写与后面可能有的volatile读/写重排序。

在每个volatile读操作的后面插入一个LoadLoad屏障。禁止下面的普通读操作和上面的volatile读操作重排序

在每个volatile读操作的后面插入一个LoadStore屏障。禁止下面的普通写操作和上面的volatile读操作重排序

3.2.2 synchronized

内存语义:

当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中.

当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量.

实现:

java对象头组成:

  1. Mark Word
  2. 指向类的指针
  3. 数组长度(只有数组对象才有)

Mark Word用于加锁操作,结构如下:

3b35666ed6a8f13e573daf7073e5d96d.png

图3.1 java对象头Mark Word

synchronized用的锁是存在Java对象头里,任何java对象都存在一个锁,JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。代码块同步是使用monitorenter和monitorexit指令实现的,monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处。

监视器锁(Monitor)本质依赖操作系统的Mutex Lock(互斥锁)来实现,如果互斥量已经上锁,调用线程会阻塞,阻塞或唤醒一条线程,都需要操作系统来帮忙完成,这就需要从用户态转换到核心态中,因此状态转换需要耗费很多的处理器时间。在jdk1.6中加入对锁的优化措施,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。锁可以升级但不能降级。

偏向锁:

当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。

轻量级锁:

轻量级锁是为了在线程近乎交替执行同步块时提高性能。多个线程竞争锁,若当前只有一个等待线程,则可通过自旋稍微等待一下,可能另一个线程很快就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁。

重量级锁:

重量级锁是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高。

其它锁优化措施:锁消除、锁粗化、自旋锁(忙循环,适用持有锁的线程很快释放锁)、自适应的自旋锁(自旋次数不固定,前一次在同一个锁上的自旋时间及锁的拥有者的状态决定)。

3.2.3 final

写final域禁止把final域的写重排序到构造函数之外。对于引用类型:在构造函数内对final域引用对象的成员域的写入,与在构造函数外将这个被构造对象的引用赋值给引用变量,这两个操作不能重排序。防止对象构造完成,未被初始化的final域被访问(要达到此目的,还需确保被构造对象不能在构造函数中“逸出”)

读final域禁止初次读一个对象的引用和随后初次读这个对象包含的final域之间的重排序。确保在读一个对象的final域前,一定会先读包含这个final域对象的引用,如果引用不为空,引用对象的final域已经被初始化过。

实现:

JMM禁止编译器把final域的写重排序到构造函数之外。

编译器在final域的写之后,构造函数return之前,插入StoreStore屏障,禁止处理器把final域的写重排序到构造函数之外。

编译器会在读final域前面插入StoreStore屏障。

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

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

相关文章

程序一旦发觉写得不理想,那就得重构它

早上有写一篇《设计模式--建造者(Builder)模式》http://www.cnblogs.com/insus/p/4179620.html。是在ASP.NET环境中,应用与演示设计模式(Builder)。现在Insus.NET从博文中最后的下载链接下载取源程序,它是有写得不够理想,现在重构它。问题点&…

template标签_C++核心准则T.65:使用标签分发提供函数的不同实现

T.65: Use tag dispatch to provide alternative implementations of a functionT.65:使用标签分发提供函数的不同实现Reason(原因)A template defines a general interface.模板定义普遍接口。Tag dispatch allows us to select implementations based on specific properties…

Windows服务器学习篇:服务器连接与退出

此文是我早期在公司内部发布的一篇给予新入职程序员基础技术培训的文章,非常基础简单,现拿出来给大家分享。当然,已工作人士可直接忽略... 一、Windows服务器连接 1. 在桌面菜单中的“运行”里,输入mstsc命令,然后回车…

nginx动静分离配置_Nginx动静分离

动静分离动静分离,就是将JSP、Servlet等动态资源交由Tomcat或其他Web服务器处理,将CSS、js、image等静态资源交由Nginx或其他Http服务器处理,充分发挥各自的优势,减轻其他服务器的压力,搭建更为高效的系统架构。Nginx动…

java thread 名称_Thread类常用方法之设置线程名称

package com.itheima.demo02.setName;/*设置线程的名称:(了解)1.使用Thread类中的方法setName(名字)void setName(String name) 改变线程名称,使之与参数 name 相同。2.创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让…

成员变量和局部变量的区别_Java 变量类型

点击上方“蓝字”带你去看小星星今天主要学习Java变量类型,主要是局部变量、实例变量和类变量(静态变量)。Java语言中,所有的变量在使用前必须声明。声明变量的基本格式如下:type identifier [ value][, identifier [ value] ...] ;格式说明…

【云图】如何制作附近实体店的地图?-微信微博支付宝

【云图】如何制作附近实体店的地图?-微信微博支付宝 原文:【云图】如何制作附近实体店的地图?-微信微博支付宝 摘要: 附近连锁店地图与全国连锁店地图,最大的区别就是: 1、附近连锁店地图需要先定位,然后搜…

php curl跨域cookie_PHP curl模拟文件上传(接口请求实现跨域文件中转)

3e2f08c0c11a8416dd107bbfc9159718.jpg客户端代码请求参数参数类型参数说明$urlstringpost提交的服务器url路径$data数组表单数据$files数组表单文件public function curl_custon_postfields($url, array $data array(), array $files array()){$curlfiles array();foreach …

oracle 分页_80分页查询,不止写法

据孔老先生说,茴香豆的茴字有四种写法,那oracle的分页查询又有多少种写法呢?分页查询,其实本质上就是topN查询的变种, 如果把topN的一部分结果集去掉,就变成了分页.topN的基本写法,两层select,第一层先order by,第二层再用rownum:select owner,object_name,object_id,rownum a…

GitHub上创建组织

4.3. 组织和团队 GitHub 在早期没有专门为组织提供账号,很多企业用户或大型开源组织只好使用普通用户账号作为组织的共享账号来使用。后来,GitHub推出了组织这一新的账号管理模式,满足大型开发团队的需要。 组织账号是不能用来登录的&#xf…

Hypothesis, 一个很少人会用的Python测试库

在软件开发中,测试是保证代码质量和功能正确性的重要环节。为了提高测试的覆盖率和效率,开发者们创造了许多测试框架和工具。其中 Hypothesis 是一个强大且受欢迎的 Python 测试库,它利用属性基测试的思想,可以自动生成测试数据并…

用户留存 数据统计 php,位运算实现用户留存率

统计留存率之前先弄清一下留存率的概念,百度百科中是这么说的: 用户在某段时间内开始使用应用,经过一段时间后,仍然继续使用应用的被认作是留存;这部分用户占当时新增用户的比例即是留存率,会按照每隔1单位…

线条边框简笔画图片大全_儿童简笔画画大全人物

由于简笔画有概括、形象、幽默、简练的特点,符合儿童生理、心理发展的需要,便于儿童接受与掌握。儿童简笔画画大全人物有哪些呢?下面由学习啦小编带来的儿童简笔画画大全人物,欢迎欣赏!儿童人物简笔画画图片大全欣赏儿童简笔画画人物图1:跳舞的小女孩简…

求两条轨迹间的hausdorff距离_题型 | 圆上有n个点到直线距离为d?

圆上有n个点到直线的距离为d圆 上到直线 的距离为 的点有( )个方法一:常规方法,画图分析由图象可以明显看出,圆在直线上方的部分内没有满足题意的点,在直线下方的部分内有两个满足题意的点。但是这样的方法…

redis setnx 分布式锁_Redis 分布式锁PHP

Redis 分布式锁的作用在单机环境下,有个秒杀商品的活动,在短时间内,服务器压力和流量会陡然上升。这个就会存在并发的问题。想要解决并发需要解决一下问题1、提高系统吞吐率也就是qps 每秒处理的请求书解决问题一:采用内存型数据库…

WinForm窗体自适应分辨率

我们自己编写程序的界面,会遇到各种屏幕分辨率,只有自适应才能显的美观。实际上,做到这点也很简单,就是首先记录窗体和它上面控件的初始位置和大小,当窗体改变比例时,其控件的位置和大小也按此比例变化即可…

ping端口_干货分享:shell脚本批量telnet ip 端口

问1:亲,请教个问题,我这边有200台服务器,怎么看它是否在线呢?答:简单,下载个PingInfoView直接批量ping下,能ping通的就在线,反之离线。问2:那怎么看我这200台…

IIS 7.0 部署MVC

开发的MVC 3.0 项目,在部署服务上还是与需要花一点功夫,这里把遇到的问题罗列出来。 本文主要介绍IIS 7.5中安装配置MVC 3.0的具体办法! 部署必备: Microsoft .net FrameWork 4.0安装包 安装ASP.NET MVC 3.0 如果 Asp.NET v4.0.30…

不是有效的函数或过程名_过程和函数

VBA代码有两种组织形式,一种是过程,另一种就是函数。其实过程和函数有很多相同之处,除了使用的关键字不同之外,还有不同的是:函数有返回值,过程没有。函数可以在Access窗体,查询中像一般的Acces…

浏览器快捷键_浏览器快捷键,让你事半功倍

随着互联网时代的发展,手机、电脑已经成为人们生活中不可或缺得一部分,无论是生活还是工作。尤其是办公室族,几乎每天都要面对电脑7/8个小时,查找各种信息或者浏览新闻,浏览器无可厚非的成为了装机必备的软件&#xff…