Java并发编程(五)-- Java内存模型补充
前面我们已经介绍了:当对象和变量存储到计算机的各个内存区域时,必然会遇到的两个问题及解决方法
- 共享对象的可见性
-- 解决方法:使用java volatile关键字 - 共享对象的竞争现象
-- 解决方法:使用java synchronized代码块
那么,volatile和 synchronized有什么区别呢?
volatile和synchronized的区别
根据JMM的实现,线程在具体执行时,会先拷贝主存数据到线程本地(CPU缓存),操作完成后再把结果从线程本地刷到主存。
synchronized关键字解决的是执行控制的问题,它会阻止其它线程获取当前对象的监控锁,这样就使得当前对象中被synchronized关键字保护的代码块无法被其它线程访问,也就无法并发执行。更重要的是,synchronized还会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性,同时也使得先获得这个锁的线程的所有操作,都happens-before于随后获得这个锁的线程的操作。
volatile关键字解决的是内存可见性的问题,会使得所有对volatile变量的读写都会直接刷到主存,即保证了变量的可见性。这样就能满足一些对变量可见性有要求而对读取顺序没有要求的需求。
使用volatile关键字仅能实现对原始变量(如boolen、 short 、int 、long等)操作的原子性,但需要特别注意, volatile不能保证复合操作的原子性,即使只是i++,实际上也是由多个原子操作组成:read i; inc; write i,假如多个线程同时执行i++,volatile只能保证他们操作的i是同一块内存,但依然可能出现写入脏数据的情况。
对于volatile关键字,当且仅当满足以下所有条件时可使用:
对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。该变量没有包含在具体其他变量的不变式中
两者的对比
- volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
- volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
- volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
- volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
支撑Java内存模型的基础原理
指令重排序
在执行程序时,为了提高性能,编译器和处理器会对指令做重排序。但是,JMM确保在不同的编译器和不同的处理器平台之上,通过插入特定类型的Memory Barrier来禁止特定类型的编译器重排序和处理器重排序,为上层提供一致的内存可见性保证。
- 编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
- 指令级并行的重排序:如果不存l在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
- 内存系统的重排序:处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
数据依赖性
如果两个操作访问同一个变量,其中一个为写操作,此时这两个操作之间存在数据依赖性。
编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序,即不会重排序。
as-if-serial
不管怎么重排序,单线程下的执行结果不能被改变,编译器、runtime和处理器都必须遵守as-if-serial语义。
内存屏障(Memory Barrier )
前面已经说过,通过内存屏障可以禁止特定类型处理器的重排序,从而让程序按我们预想的流程去执行。内存屏障,又称内存栅栏,是一个CPU指令,基本上它是一条这样的指令:
- 保证特定操作的执行顺序。
- 影响某些数据(或则是某条指令的执行结果)的内存可见性。
编译器和CPU能够重排序指令,保证最终相同的结果,尝试优化性能。插入一条Memory Barrier会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序。
Memory Barrier所做的另外一件事是强制刷出各种CPU cache,如一个Write-Barrier(写入屏障)将刷出所有在Barrier之前写入 cache 的数据,因此,任何CPU上的线程都能读取到这些数据的最新版本。
这和java有什么关系?上面java内存模型中讲到的volatile是基于Memory Barrier实现的。
如果一个变量是volatile修饰的,JMM会在写入这个字段之后插进一个Write-Barrier指令,并在读这个字段之前插入一个Read-Barrier指令。这意味着,如果写入一个volatile变量,就可以保证:
- 一个线程写入变量a后,任何线程访问该变量都会拿到最新值。
- 在写入变量a之前的写入操作,其更新的数据对于其他线程也是可见的。因为Memory Barrier会刷出cache中的所有先前的写入。
happens-before
从jdk5开始,java使用新的JSR-133内存模型,基于happens-before的概念来阐述操作之间的内存可见性。
在JMM中,如果一个操作的执行结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,这个的两个操作既可以在同一个线程,也可以在不同的两个线程中。
与程序员密切相关的happens-before规则如下:
- 程序顺序规则:一个线程中的每个操作,happens-before于该线程中任意的后续操作。
- 监视器锁规则:对一个锁的解锁操作,happens-before于随后对这个锁的加锁操作。
- volatile域规则:对一个volatile域的写操作,happens-before于任意线程后续对这个volatile域的读。
- 传递性规则:如果 A happens-before B,且 B happens-before C,那么A happens-before C。
注意:两个操作之间具有happens-before关系,并不意味前一个操作必须要在后一个操作之前执行!仅仅要求前一个操作的执行结果,对于后一个操作是可见的,且前一个操作按顺序排在后一个操作之前。
参考资料:
http://www.infoq.com/cn/articles/java-memory-model-1
https://kzx1025.github.io/2017/04/06/java-memory-model/
Java并发编程(五)-- Java内存模型补充的更多相关文章
- Java并发编程:Java的四种线程池的使用,以及自定义线程工厂
目录 引言 四种线程池 newCachedThreadPool:可缓存的线程池 newFixedThreadPool:定长线程池 newSingleThreadExecutor:单线程线程池 newS ...
- Java并发编程(1)-Java内存模型
本文主要是学习Java内存模型的笔记以及加上自己的一些案例分享,如有错误之处请指出. 一 Java内存模型的基础 1.并发编程模型的两个问题 在并发编程中,需要了解并会处理这两个关键问题: 1.1.线 ...
- 【Java并发编程五】信号量
一.概述 技术信号量用来控制能够同时访问某特定资源的活动的数量,或者同时执行某一给定操作的数据.计数信号量可以用来实现资源池或者给一个容器限定边界. 信号量维护了一个许可集,许可的初始量通过构造函数传 ...
- Java并发编程:Java内存模型JMM
简介 Java内存模型英文叫做(Java Memory Model),简称为JMM.Java虚拟机规范试图定义一种Java内存模型来屏蔽掉各种硬件和系统的内存访问差异,实现平台无关性. CPU和缓存一 ...
- Java并发编程--7.Java内存操作总结
主内存和工作内存 工作规则 Java内存模型, 定义变量的访问规则, 即将共享变量存储到内存和取出内存的底层细节 所有的变量都存储在主内存中,每条线程有自己的工作内存,工作内存中用到的变量, 是从主 ...
- Java并发编程:Java线程池核心ThreadPoolExecutor的使用和原理分析
目录 引出线程池 Executor框架 ThreadPoolExecutor详解 构造函数 重要的变量 线程池执行流程 任务队列workQueue 任务拒绝策略 线程池的关闭 ThreadPoolEx ...
- Java并发编程:Java中的锁和线程同步机制
锁的基础知识 锁的类型 锁从宏观上分类,只分为两种:悲观锁与乐观锁. 乐观锁 乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新 ...
- Java并发编程-深入Java同步器AQS原理与应用-线程锁必备知识点
并发编程中我们常会看到AQS这个词,很多朋友都不知道是什么东东,博主经过翻阅一些资料终于了解了,直接进入主题. 简单介绍 AQS是AbstractQueuedSynchronizer类的缩写,这个不用 ...
- Java并发编程:Java实现多线程的几种方式
在Java中,多线程主要的实现方式有四种:继承Thread类.实现Runnable接口.实现Callable接口通过FutureTask包装器来创建Thread线程.使用ExecutorService ...
- Java并发编程 (五) 线程安全性
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.安全发布对象-发布与逸出 1.发布与逸出定义 发布对象 : 使一个对象能够被当前范围之外的代码所使用 ...
随机推荐
- Spring Boot如何使用Runner实现启动时调用?用法和原理都在这里
在日常的项目开发中经常会遇到这样的需求:项目启动的时候进行一些一次性的初始化工作,如读取加载资源文件.或者执行其它外部程序. 这个时候我们就可以用到spring-boot为我们提供的一种扩展机制--R ...
- linux里面的命令:./和. /(这里有空格)的区别
/ ( 直接/ ) 直接 / 表示: 根目录. ./ ( 点号紧跟/ ) 点号紧跟/ 表示: 当前目录(相对路径的写法). . / ( 点号 空格 / ) 有空格的点号等同于source命令,表示在 ...
- easyui实现背景图片半透明状态,悬浮在大背景之上
首先是查找素材,使用AI将所需要的图案画出来,切记将图案的背景设置为所需要的透明状态.项目使用的是easyui架构 为啥加两个背景图呢,因为这样的布局最开始是给一个矩形框加上的背景图片,若是还使用矩形 ...
- SpringBoot系列七:SpringBoot 整合 MyBatis(配置 druid 数据源、配置 MyBatis、事务控制、druid 监控)
1.概念:SpringBoot 整合 MyBatis 2.背景 SpringBoot 得到最终效果是一个简化到极致的 WEB 开发,但是只要牵扯到 WEB 开发,就绝对不可能缺少数据层操作,所有的开发 ...
- Django复习
django复习 django和其他框架的对比 django,集成很多功能的大型框架,为开发者提供了很多方便的组件:orm.form.ModelForm.auth.admin.contenttypes ...
- map映射巧用 A-B Problems
A-B problem Description 大家都非常熟悉 A+B Problem! 题目看多了也有审美疲劳,于是我舍弃了,改用 A-B problem! 题目是这样的:给出一串数以及一个数字 C ...
- Visual Studio上编译ncnn
prerequisite 是为了在PC上熟悉ncnn的基本代码,所以用Visual Studio来配置的. 期间用过VS2013(update5)/VS2015/VS2017,反正都是基于CMake生 ...
- C#获取一周的工作日显示(星期几)
代码如下: gridBandW1.Caption = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.GetDayName ...
- 根据现有的XML文件生成其对应的实体类
方法如下: 1.将完整的Xml文本复制一下, 2.在vs2013(或以上版本) .net4.5项目下建立一个类文件, 3.依次选择菜单:编辑->选择性粘贴->将XML粘贴为类.
- Windows配置Apache服务器
下载的Apache版本:httpd-2.4.32-o102n-x64-vc14 简单粗暴式配置: 1.打开\conf\httpd.conf修改第60行和第224行,其中8081是电脑上没有被使用的端口 ...