java并发编程实战:第十六章----Java内存模型
一、什么是内存模型,为什么要使用它
如果缺少同步,那么将会有许多因素使得线程无法立即甚至永远看到一个线程的操作结果
- 编译器把变量保存在本地寄存器而不是内存中
- 编译器中生成的指令顺序,可以与源代码中的顺序不同
- 处理器采用乱序或并行的方式来执行指令
- 保存在处理器本地缓存中的值,对于其他处理器是不可见
在单线程中,只要程序的最终结果与在严格串行环境中执行的结果相同,那么上述所有操作都是允许的
在多线程中,JVM通过同步操作来找出这些协调操作将在何时发生
JMM规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对其他线程可见
1、平台的内存模型
每个处理器都拥有自己的缓存,并且定期地与主内存进行协调,在不同的处理器架构中提供了不同级别的缓存一致性,即允许不同的处理器在任意时刻从同一个存储位置上看到不同的值。JVM通过在适当的位置上插入内存栅栏来屏蔽在JMM与底层平台内存模型之间的差异。Java程序不需要指定内存栅栏的位置,而只需通过正确地使用同步来找出何时将访问共享状态
2、重排序
各种使操作延迟或者看似乱序执行的不同原因,都可以归为重排序,内存级的重排序会使程序的行为变得不可预测
1 Thread one = new Thread(new Runnable() { 2 public void run() { 3 a = 1; 4 x = b; 5 } 6 });
上述代码也会有以下结果:
3、Java内存模式简介
Java内存模型是通过各种操作来定义的,包括变量的读/写操作,监视器的加锁和释放操作,以及线程的启动和合并操作
JMM为程序中所有的操作定义了一个偏序关系,称为Happens-Before,使在正确同步的程序中不存在数据竞争(缺乏Happens-Before关系,那么JVM可以对它们任意地重排序)
- 程序顺序规则。如果程序中操作A在操作B之前,那么在线程中A操作将在B操作之前执行
- 监视器锁规则。在监视器锁上的解锁操作必须在同一个监视器锁上的加锁操作之前执行。(显式锁和内置锁在加锁和解锁等操作上有着相同的内存语义)
- volatile变量规则。对volatile变量的写入操作必须在对该变量的读操作之前执行。(原子变量与volatile变量在读操作和写操作上有着相同的语义)
- 线程启动规则。在线程上对Thread.start的调用必须在该线程中执行任何操作之前执行
- 线程结束规则。线程中的任何操作都必须在其他线程检测到该线程已经结束之前执行,或者从Thread.join中成功返回,或者在调用Thread.isAlive时返回false
- 中断规则。当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行(通过抛出InterruptException,或者调用isInterrupted和interrupted)
- 终结器规则。对象的构造函数必须在启动该对象的终结器之前执行完成
- 传递性。如果操作A在操作B之前执行,并且操作B在操作C之前执行,那么操作A必须在操作C之前执行。
4、借助同步
”借助(Piggyback)“现有同步机制的可见性属性,对某个未被锁保护的变量的访问操作进行排序(不希望给对象加锁,而又想维护它的顺序)
Happens-Before排序包括:
- 将一个元素放入一个线程安全容器的操作将在另一个线程从该容器中获得这个元素的操作之前执行
- 在CountDownLatch上的倒数操作将在线程从闭锁上的await方法返回之前执行
- 释放Semaphore许可的操作将在从该Semaphore上获得一个许可之前执行
- Future表示的任务的所有操作将在从Future.get中返回之前执行
- 向Executor提交一个Runnable或Callable的操作将在任务开始执行之前执行
- 一个线程到达CyclicBarrier或Exchange的操作将在其他到达该栅栏或交换点的线程被释放之前执行。如果CyclicBarrier使用一个栅栏操作,那么到达栅栏的操作将在栅栏操作之前执行,而栅栏操作又会在线程从栅栏中释放之前执行
二、发布
造成不正确发布的真正原因:"发布一个共享对象"与"另一个线程访问该对象"之间缺少一种Happens-Before的关系
1、不安全的发布
除了不可变对象以外,使用被另一个线程初始化的对象通常都是不安全的,除非对象的发布操作是在使用该对象的线程开始使用之前执行
1 public class UnsafeLazyInitialization {2 private static Object resource;3 4 public static Object getInstance(){5 if (resource == null){6 resource = new Object(); //不安全的发布7 }8 return resource;9 } 10 }
原因一:线程B看到了线程A发布了一半的对象
原因二:即使线程A初始化Resource实例之后再将resource设置为指向它,线程B仍可能看到对resource的写入操作将在对Resource各个域的写入操作之前发生。因为线程B看到的线程A中的操作顺序,可能与线程A执行这些操作时的顺序并不相同
2、安全发布
例:BlockingQueue的同步机制保证put在take后执行,A线程放入对象能保证B线程取出时是安全的
借助于类库中现在的同步容器、使用锁保护共享变量、或都使用共享的volatile类型变量,都可以保证对该变量的读取和写入是按照happens-before排序的
happens-before事实上可以比安全发布承诺更强的可见性与排序性
3、安全初始化模式
方式一:加锁保证可见性与排序性,存在性能问题
1 public class UnsafeLazyInitialization {2 private static Object resource;3 4 public synchronized static Object getInstance(){5 if (resource == null){6 resource = new Object(); //不安全的发布7 }8 return resource;9 } 10 }
方式二:提前初始化,可能造成浪费资源
1 public class EagerInitialization { 2 private static Object resource = new Object(); 3 public static Object getInstance(){ 4 return resource; 5 } 6 }
方式三:延迟初始化,建议
1 public class ResourceFactory { 2 private static class ResourceHolder{ 3 public static Object resource = new Object(); 4 } 5 public static Object getInstance(){ 6 return ResourceHolder.resource; 7 } 8 }
方式四:双重加锁机制,注意保证volatile类型,否则出现一致性问题
1 public class DoubleCheckedLocking {2 private static volatile Object resource;3 public static Object getInstance(){4 if (resource == null){5 synchronized (DoubleCheckedLocking.class){6 if (resource == null){7 resource = new Object(); 8 }9 } 10 } 11 return resource; 12 } 13 }
三、初始化过程中的安全性
- 如果能确保初始化过程的安全性,被正确构造的不可变对象在没有同步的情况下也能安全地在多个线程之间共享
- 如果不能确保初始化的安全性,一些本应为不可变对象的值将会发生改变
初始化安全性只能保证通过final域可达的值从构造过程完成时可见性。对于通过非final域可达的值,或者在构成过程完成后可能改变的值,必须采用同步来确保可见性
转载于:https://www.cnblogs.com/linghu-java/p/9053155.html
java并发编程实战:第十六章----Java内存模型相关推荐
- 【Java并发编程】之十六:深入Java内存模型——happen-before规则及其对DCL的分析(含代码)...
Java并发编程系列 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/mmc_maodun/article/details/17348313 转载请注 ...
- 转:【Java并发编程】之十六:深入Java内存模型——happen-before规则及其对DCL的分析(含代码)...
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17348313 happen-before规则介绍 Java语言中有一个"先行发生 ...
- 《java并发编程实战》第11章-性能与可伸缩性
11.1 对性能的思考 11.1.1 性能的提升意味着什么 提升性能意味着用更少的资源做更多的事情, 在任何时刻,提升性能的前提是要保证程序的正确性 当操作由于某种特定的资源而受到限制时,通常就称该操 ...
- 夜光 带你走进 Java基础编程实战(十六 Applet编程)
夜光序言: "你喜欢.这个世界吗?" "无关喜欢,只是早就已经放弃了." --因为世界放弃了他,所以他只能放弃世界. 正文:程序的魅力~~ package co ...
- 【极客时间】《Java并发编程实战》学习笔记
目录: 开篇词 | 你为什么需要学习并发编程? 内容来源:开篇词 | 你为什么需要学习并发编程?-极客时间 例如,Java 里 synchronized.wait()/notify() 相关的知识很琐 ...
- Java并发编程实战————Executor框架与任务执行
引言 本篇博客介绍通过"执行任务"的机制来设计应用程序时需要掌握的一些知识.所有的内容均提炼自<Java并发编程实战>中第六章的内容. 大多数并发应用程序都是围绕&qu ...
- Java并发编程实战_不愧是领军人物!这种等级的“Java并发编程宝典”谁能撰写?...
前言 大家都知道并发编程技术就是在同一个处理器上同时的去处理多个任务,充分的利用到处理器的每个核心,最大化的发挥处理器的峰值性能,这样就可以避免我们因为性能而产生的一些问题. 大厂的核心负载肯定是非常 ...
- 视频教程-Java并发编程实战-Java
Java并发编程实战 2018年以超过十倍的年业绩增长速度,从中高端IT技术在线教育行业中脱颖而出,成为在线教育领域一匹令人瞩目的黑马.咕泡学院以教学培养.职业规划为核心,旨在帮助学员提升技术技能,加 ...
- Java并发编程实战基础概要
文章目录 Java并发编程实战基础概要 开篇 多线程问题有啥难点呢? 为啥要学习并发编程? 并发问题的根源是什么? CPU切换线程执导致的原子性问题是如何发生的? 缓存导致的可见性问题是如何发生的? ...
- Java并发编程实战————恢复中断
中断是一种协作机制,一个线程不能强制其他线程停止正在执行的操作而去执行其他操作. 什么是中断状态? 线程类有一个描述自身是否被中断了的boolean类型的状态,可以通过调用 .isInterrupte ...
最新文章
- 40亿移动设备的用户画像和标签架构实践
- React Native ios打包
- 1057 Stack (30 分)【难度: 中 / 知识点: 树状数组 STL】
- vue中模板编译compiler源码详解
- Linux设置Oracle开机自启动
- MySQL表级约束和列级约束
- 利用Jquery将表单序列化成JS对象
- Storm目录树和任务提交过程
- 使用MySQL中的对象数组查询JSON列
- 集成Slice2Java插件
- 凯撒密码中的字符转换
- App上架各大应用市场的地址及操作方法
- 如何调用Sphinx
- vijos 1540 月亮之眼 并查集
- 服务都微了,编排怎么整?
- 将Macbook的光驱位升级成SSD
- 【karle 笔记】无法通过Windows功能控制面板自动安装或卸载Windows Server 角色和功能。
- sql server 字符串和日期的转换函数
- 隔直电容选取及大小选择
- 苏宁易购按关键字搜索suning商品 API 返回值说明
热门文章
- 我要学ASP.NET MVC 3.0(一): MVC 3.0 的新特性
- 推荐一个HTML的语法高亮解析器
- 《OpenCV3编程入门》学习笔记6 图像处理(三)形态学滤波(1):腐蚀与膨胀
- ORB特征提取算法分析与实现,算法分解
- linux longlong大小,Linux基本数据类型大小——int,char,long int,long long int
- python我的所得税计算器_教你使用Python实现新个税计算器
- php列表显示教程,Dedecms后台管理文档列表显示自定义字段方法教程
- presto能连接mysql和hive吗_Presto连接Hive
- 一位中学计算机老师的英语作文,我的老师英语作文(精选14篇)
- java初始化数据报_初始化java原因