那些年读过的书《Java并发编程实战》十、再探究Java内存模型
1、什么是内存模型,为什么需要它?
(1)内存模型的发展背景
近几年计算性能通过重排序实现了很大的提升,而且处理器也越来越朝着多核处理器发展以实现硬件的并行性。随着处理器的不断强大,编译器也在不断的改进:通过指令重排序来实现优化执行,使用成熟的全局寄存器分配
算法。这些都使得线程在内存内的操作更趋于复杂,如果没有正确的同步机制下,内存间的操作呈现乱序执行,从而不能保证计算结果的正确性。
编译器重排序:使得编译后的指令顺序可以和源代码的顺序不一样
处理器重排序:在编译器重排序的基础上再进行指令的优化重排序
处理器的并行性:多线程内线程之间的操作执行顺序不同,要实现线程间的数据共享在没有同步的机制下易产生不安全的数据共享。
处理器的多级缓存:如果缓存提交至主内存的顺序没有一定的同步机制下,就会出现提交顺序的乱序,使得共享数据在内存的不可见性。
1)对单线程程序执行的影响
在指令重排序的情况下,只要保证最终的计算结果和严格的串行执行环境下的结果一致下,重排序等优化措施是可以的。
2)对多线程程序执行的影响
多线程中线程间各自的操作执行顺序不同,强行保持程序的串行性,只会增加线程间的调度次数,而频繁的线程调度会引起不要的上下文操作使得线程的开销很大也降低了程序的执行速度。在只需要进行数据共享的操作内使
用同步机制协调线程间的操作,实现线程间的数据共享(保存线程间短暂的串行性)就能够减少不要的性能开销。
(2)Java内存模型是处理器架构内存模型的抽象
Java内存模型屏蔽了不同处理器架构内存模型之间的差异(JVM通过插入内存栅栏来屏蔽JMM与底层平台内存模型之间的差异)
(3)重排序
1)进行重排序的条件:操作之间不存在偏序关系和全序关系
2)禁止重排序的方法:锁,volatile,final
重排序会破外操作之间的偏序关系(Happens-Before),从而产生数据竞争问题
(4)内存模型简介
1)简介:
Java内存模型是通过各种操作来定义的,包括对变量的读写操作,监视器的加锁和释放操作,以及现场的启动和合并操作。
Java内存模型实质是为最终计算结果的正确性,而采取的实现内存操作间保存偏序关系即实现内存操作的有序性,实现内存操作的内存可见性和原子性的内存访问操作的模型
2)偏序关系(Happens-Before):
偏序关系(Happens-Before)的本质:保证操作之间的内存可见性
保持偏序关系(Happens-Before)的准则:
规则名称
内容
说明
程序顺序规则 程序代码顺序自然保持操作间的偏序关系 监视器锁规则 在同一个锁上锁的释放肯定在锁的获取之后 volatile规则 volatile变量的写操作肯定在volatile变量的读操作之前 线程启动规则 线程的启动操作start肯定在线程执行操作之前 线程结束规则 线程执行的任何操作肯定在线程检测到该线程结束之前执行,或者从join操作或者调用Thread.isAlive是返回 中断规则 interrupt中断操作必须在线程检测到线程中断之前执行 终结器规则 对象的构造函数必须在对象的终结器执行之前执行 传递性 操作顺序的传递性
2.发布
(1)不安全发布的本质
不安全发布操作的本质就是:对象的发布操作和对象的访问操作之间缺乏偏序关系(Happens-Before排序)
除了不可变对象外,使用被另外一个线程初始化的对象通常都是不安全的,除非对象的发布操作是在使用该对象的线程开始使用之前执行(即保存对象的发布操作在对象的加载操作之前也就是两个操作间保持偏序关系)
(2)安全发布的本质
安装发布对象的本质就是:使用锁或者volatile关键字来禁止操作之间的重排序,从而保持操作之间的偏序关系
(3)安全的初始化模式
初始化的几种模式:
初始化模式 |
优点 |
缺点 |
备注 |
synchronized加锁模式 | 禁止重排序,实现了线程安全的初始化 降低了初始化类或者创建实例的开销 |
数据竞争严重的情况下太耗性能 增加了访问被初始化延迟的字段的开销 |
不推荐使用 |
类初始化加锁模式 | 在类的静态初始化过程完成初始化,实现了线程安全的初始化 降低了初始化类或者创建实例的开销 |
仅适用于在构造时的状态,对于可变的对象读写操作之间仍然需要同步机制。仅限于静态字段的初始化 增加了访问被初始化延迟的字段的开销 |
类初始化期间获得一个锁,并且每个线程都至少获取一次这个锁以确保这个类已经加载。类初始化(静态初始化)时类在初始化阶段执行,在类型加载后被线程使用之前执行。 |
双重检测锁定模式(DCL) | 降低了因synchronize锁带来的性能消耗 降低了初始化类或者创建实例的开销 |
没有实现线程安全的初始化 | 错误的不安全的延迟初始化方案 |
基于volatile改良后的双重检测锁定模式(VDCL) | 降低了因synchronize锁带来的性能消耗,实现了线程安全的初始化 除了可用于静态字段也可以用于实例字段 降低了初始化类或者创建实例的开销 |
增加了访问被初始化延迟的字段的开销 |
无论通过类初始化加锁模式还是volatile关键字都是实现了禁止重排序或者实现操作间的偏序关系从而保证了操作内存的可见性。
3、初始化过程中的安全性
初始化安全性只能保证final修饰的值从构造过程完成时开始的可见性,对于非final的值,或者在构造完成后可以改变的值,必须采用同步来确保可见性
那些年读过的书《Java并发编程实战》十、再探究Java内存模型的更多相关文章
- 《java并发编程实战》读书笔记13--Java内存模型,重排序,Happens-Before
第16章 Java内存模型 终于看到这本书的最后一章了,嘿嘿,以后把这本书的英文版再翻翻.这本书中尽可能回避了java内存模型(JMM)的底层细节,而将重点放在一些高层设计问题,例如安全发布,同步策略 ...
- 那些年读过的书《Java并发编程实战》和《Java并发编程的艺术》三、任务执行框架—Executor框架小结
<Java并发编程实战>和<Java并发编程的艺术> Executor框架小结 1.在线程中如何执行任务 (1)任务执行目标: 在正常负载情况下,服务器应用 ...
- java并发编程实战《二》java内存模型
Java解决可见性和有序性问题:Java内存模型 什么是 Java 内存模型? Java 内存模型是个很复杂的规范,可以从不同的视角来解读,站在我们这些程序员的视角,本质上可以理解为, Java 内存 ...
- Java并发编程实战 第16章 Java内存模型
什么是内存模型 JMM(Java内存模型)规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对其他线程可见. JMM为程序中所有的操作定义了一个偏序关系,称为Happens-Be ...
- Java并发编程(十四)Java内存模型
1.共享内存和消息传递 线程之间的通信机制有两种:共享内存和消息传递:在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程 ...
- 《Java并发编程实战》文摘
更新时间:2017-06-03 <Java并发编程实战>文摘,有兴趣的朋友可以买本纸质书仔细研究下. 一 线程安全性 1.1 什么是线程安全性 当多个线程访问某个类时,不管运行时环境采用何 ...
- Java并发编程实战——读后感
未完待续. 阅读帮助 本文运用<如何阅读一本书>的学习方法进行学习. P15 表示对于书的第15页. Java并发编程实战简称为并发书或者该书之类的. 熟能生巧,不断地去理解,就像欣赏一部 ...
- 【Java并发编程实战】-----“J.U.C”:ReentrantReadWriteLock
ReentrantLock实现了标准的互斥操作,也就是说在某一时刻只有有一个线程持有锁.ReentrantLock采用这种独占的保守锁直接,在一定程度上减低了吞吐量.在这种情况下任何的"读/ ...
- 【Java并发编程实战】-----“J.U.C”:Semaphore
信号量Semaphore是一个控制访问多个共享资源的计数器,它本质上是一个"共享锁". Java并发提供了两种加锁模式:共享锁和独占锁.前面LZ介绍的ReentrantLock就是 ...
- 《Java并发编程实战》/童云兰译【PDF】下载
<Java并发编程实战>/童云兰译[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062521 内容简介 本书深入浅出地介绍了Jav ...
随机推荐
- 驱动文件中只有cat/inf/dll文件,怎么安装
网上下载了一个驱动,里面包含文件只有cat/inf/dll文件,怎么安装? 1.计算机-右键-管理-设备管理器,找到要装驱动的设备上 2.右键-更新驱动程序-浏览到本地的这个驱动文件夹 3.开始安装
- 仿迅雷播放器教程 -- C++ 100款开源界面库 (10)
(声明:Alberl以后说到开源库,一般都是指著名的.或者不著名但维护至少3年以上的.那些把代码一扔就没下文的,Alberl不称之为开源库,只称为开源代码.这里并不是贬低,像Alberl前面那个系 ...
- metroui
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- windows系统下,express构建的node项目中,如何用debug控制调试日志
debug是一款控制日志输出的库,可以在开发调试环境下打开日志输出,生产环境下关闭日志输出.这样比console.log方便多了,console.log只有注释掉才能不输出. debug库还可以根据d ...
- 【NLP】分词 新词
基于大规模语料的新词发现算法 https://blog.csdn.net/xgjianstart/article/details/52193258 互联网时代的社会语言学:基于SNS的文本数据挖掘 h ...
- k8s(1)-使用kubeadm安装Kubernetes
安装前准备 1. 一台或多台主机,这里准备三台机器 角色 IP Hostname 配置(最低) 操作系统版本 主节点 192.168.0.10 master 2核2G CentOS7.6.1810 工 ...
- 试一下Markdown
Markdown 没想到博客园居然能够有markdown这样的写法了,以前觉得有自定义CSS已经非常不错了,现在居然加入Markdown,太值得称赞了.国内的博客系统,应该首屈一指了. 强调 你要走, ...
- inittab 解析
Linux完成内核(Kernel)引导后,会由init初始化进程调用/etc/inittab配置文件(ps -aux | less,init进程号为始终为1,是所有系统进程的起点,init进程也有一个 ...
- Ubuntu下U盘变成只读的解决方法
首先执行命令: tail -f /var/log/syslog 然后插入有问题的U盘,tail会打印一些log: Jan :: zkw- kernel: [-: new high-speed USB ...
- python3 日志检索异常抛出异常 raise KeyError(key),KeyError: 'formatters'
原因分析,python3启动时,检索的不是项目包下的目录文件,所以日志文件必须指明路径.我原来的日志方式是: import logging.config log_path = "mlpcap ...