`
足至迹留
  • 浏览: 485432 次
  • 性别: Icon_minigender_1
  • 来自: OnePiece
社区版块
存档分类
最新评论

<高级-2> java内存模型

阅读更多
抓住4月尾巴:)

前面介绍的所有原则,比如安全发布,同步策略的规范以及一致性等,他们的安全性都来自于JMM(java内存模型, java memory model)。

1.1 什么是内存模型,为什么需要它
假设一个线程为变量aVariable赋值:
aVariable = 3;
内存模型需要解决这个问题:在什么条件下,读取aVariable的线程将看到这个值为3?在缺少同步的条件下,会有许多因素使得线程无法立即,甚至永远,看到另一个线程的操作结果。
(1)在编译器中生成的指令顺序,可以与源代码的顺序不同,此外编译器还会把变量保存在寄存器而不是内存中;
(2)处理器可以采用乱序或并行等方式来执行指令;
(3)缓存(每个工作线程都有自己的缓存,多处理器时每个处理器也有自己的缓存)可能会改变将写入变量提交到主内存的次序;
(4)而且,保存在处理器本地缓存中的值,对于其他处理器是不可见的。这些因素都会使得一个线程无法看到变量的最新值,并且会导致其他线程中的内存操作似乎在乱序执行(如果没有使用正确的同步)。

JMM规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对于其他线程可见。JMM在设计时就在可预测性和程序的易开发性之间进行了权衡,从而在各种主流的处理器体系架构上能实现高性能的JVM。

1.1.1 平台的内存模型
在共享内存的多处理器体系架构中,每个处理器都拥有自己的缓存,并且定期地与主内存进行协调。在不同的处理器架构中提供了不同级别的缓存一致性(Cache Coherence),其中一部分只提供最小的保证即允许不同的处理器在任意时刻从同一个存储位置上看到不同的值
在架构定义的内存模型中将告诉应用程序可以从内存系统中获得怎样的保证,此外还定义了一些特殊的指令(称为内存栅栏或栅栏),当需要共享数据时,这些指令就能实现额外的存储协调保证。为了使java开发人员无需关心不同架构上内存模型之间的差异,java还提供了自己的内存模型,并且jvm通过在适当的位置上插入内存栅栏来屏蔽在JMM与底层平台内存模型之间的差异。

1.1.2 重排序
下面程序说明了在没有正确同步的情况下,即使要推断最简单的并发程序的行为也很困难。这个程序就可以输出(1,0),(1,1),(0,1),(0,0)。前三种情况很容易想象,


1.1.3 java内存模型简介
java内存模型是通过各种操作来定义的,包括对变量的读/写操作,监视器的加锁和释放操作,以及线程的启动和合并操作。JMM为程序总所有的操作定义了一个偏序关系,称之为Happens-Before。要想保证执行B操作的线程看到操作A的结果(无论A和B是否在同一个线程中执行),那么在A和B之间必须满足Happens-Before关系。如果两个操作之间缺乏Happens-Before关系,那么JVM可以对它们任意地排序。
当一个变量被多个线程读取并且至少被一个线程写入时,如果在读操作和写操作之间没有依照Happens-Before来排序,那么就会产生数据竞争问题。在正确同步的程序中不存在数据竞争,并且表现出串行一致性,这意味着程序中的所有操作都会按照一种固定的和全局的顺序执行。
Happens-before规则包括:
程序顺序规则:如果程序中操作A在操作B之前,那么在线程中A操作在B操作之前执行。
监视器锁规则:在监视器锁上的解锁操作必须在同一个监视器锁上的加锁操作之前执行。
volatile变量规则:对volatile变量的写入操作必须在对该变量的读操作之前执行。推荐: http://www.cnblogs.com/dolphin0520/p/3920373.html
线程启动规则:在线程上对Thread.start的调用必须在该线程中执行任何操作之前执行。
线程结束规则:线程中的任何操作都必须在其他线程检测到该线程已经结束之前执行,或者从Thread.join中成功返回,或者在调用Thread.isAlive时返回false.
中断规则:当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行(通过抛出InterruptedException,或者调用isInterrupted和interrupted)。
终结器规则:对象的构造函数必须在启动该对象的终结器之前执行完成。
传递性:如果操作A在操作B之前执行,并且操作B在操作C之前执行,那么操作A也必须在操作C之前执行。

虽然这些操作只满足偏序关系,但同步操作,如锁的获取与释放等操作,以及volatile变量的读取和写入操作,都满足全序关系


1.2 发布
前面介绍的如何安全地发布一个对象,他们的安全性都来自于JMM提供的保证,而造成不正确发布的真正原因,就是“发布一个共享对象”与“另一个线程访问该对象”之间缺少一种Happens-Before排序。

1.2.1 不安全的发布
缺少Happens-Before关系时,就可能出现重排序问题,这就解释了为什么在没有充分同步的情况下发布一个对象会导致另一个线程看到一个只被部分构造的对象。
(1)在初始化一个新的对象时,需要写入多个变量,即新对象中的各个域。
(2)同样,在发布一个引用时也需要写入一个变量,即新对象的引用。
依据上面两步,如果无法确保发布共享引用的操作在另一个线程加载该共享引用之前执行,那么对新对象引用的写入操作将与对象中各个域的写入操作重排序。在这种情况下,另一个线程可能看到对象引用的最新值,但同时也将看到对象的某些或全部状态中包含的无效值,即一个被部分构造的对象。

错误的延迟初始化将导致不正确的发布。

这是最常见的不安全的延迟初始化方式。

除了不可变对象以外,使用被另一个线程初始化的对象通常都是不安全的,除非对象的发布操作是在使用对象的线程开始使用之前执行。

1.2.2 安全初始化模式
有时候我们需要推迟一些高开销的对象初始化操作,并且只有当使用这些对象时才进行初始化,但我们也看到了在误用延迟初始化时导致的问题。
给上面的程序加上同步约束,就成了线程安全的初始化。


在初始器中采用了特殊的方式来处理静态域(或在静态初始化代码块中初始化的值),并提供了额外的线程安全性保证。静态初始化器(也就是static块)是由JVM在类的初始化阶段执行,即在类被加载后并且被线程使用前。由于JVM将在初始化阶段获得一个锁,并且每个线程都至少获取一次这个锁以确保这个类已经加载,因此在静态初始化期间,内存写入操作将自动对所有线程可见。因此无论是在被构造期间还是被引用时,静态初始化的对象都不需要显式的同步。然而,这个规则仅适用于在构造时的状态,如果对象是可变的,那么在读线程和写线程之间仍然需要通过同步来确保随后的修改操作时可见的以及避免数据破坏。
如下还有两种方式可以安全的构造对象。一个是结合静态初始化技术提前初始化对象,一个是使用延迟初始化占位类模式(使用一个专门的类来初始化对象)。



JVM将推迟ResourceHolder的初始化操作,直到开始使用这个类时才初始化,并且由于通过一个静态初始化来初始化Resource,因此不需要额外的同步。
看到没,第二种方法既做到了延迟初始化,又不需要使用同步,非常实用!

1.2.3 双重检查锁(DCL, double check lock)
任何一本介绍并发的书都会讨论声名狼藉的双重检查锁。


上面这样写,线程可能看到一个仅被部分初始化构造的Resource。DCL的真正问题在于,当在没有同步的情况下读取一个共享对象时,可能发生的最糟糕事情只是看到一个失效值。然而,实际情况远比这更糟糕,线程可能看到引用的当前值,但对象的状态值却是失效的,这意味着线程可以看到对象处于无效或错误的状态。

在JMM的后续版本(jdk5.0及以后),如果把resource声明为volatile类型,那么DCL就是安全的,并且对性能的影响很小。然而,DCL的这种使用方法已经被广泛废弃了,延迟初始化占位类模式能带来同样的优势,而且更容易理解。推荐: http://www.iteye.com/topic/652440

1.3 初始化过程中的安全性
如果能确保初始化过程的安全性,那么就可以使得被正确构造的不可变对象在没有同步的情况下也能安全地在多个线程之间共享,而不管是如何发布的,甚至通过某种数据竞争来发布。也即是如果前面的例子Resource是不可变的,那么不安全的延迟初始化写法实际上也是安全的。
如果不能确保初始化的安全性,那么当在发布或线程中没有使用同步时,一些本应该为不可变对象的值将会发生改变。安全性架构依赖于String的不可变形,如果缺少了初始化安全性,那么可能会导致一个安全漏洞,从而使恶意代码绕过安全检查。

对于含有final域的对象,初始化安全性可以防止对对象的初始引用被重排序到构造过程之前。当构造函数完成时,构造函数对final的所有写入操作,以及对通过这些域可以到达的任何变量的写入操作都将被冻结。并且任何获得该对象引用的线程都至少能确保看到被冻结的值。对于通过final域可到达的初始变量的写入操作,将不会与构造过程后的操作仪器被重排序。

初始化安全性意味着,下面程序的SafeStates可以安全地发布,即使通过不安全的延迟初始化,或者在没有同步的情况下将SafeStates的引用放到一个公有的静态域,或者没有使用同步以及依赖于非线程安全的HashSet.


然而,许多对SafeStates的细微修改都可能破坏它的线程安全性。如果states不是final类型,或者存在除构造函数以外的其他方法能修改states,那么初始化安全性将无法确保。如果在SafeStates中还有其他的非final域,那么其他线程仍然可能看到这些域上不正确的值。这也导致了对象在构造函数中逸出。

初始化安全性只能保证通过final域可达的值从构造过程完成时开始的可见性。对于通过非final域可达的值,或者在构成过程完成后可能改变的值,必须采用同步来确保可见性。
  • 大小: 98.4 KB
  • 大小: 74 KB
  • 大小: 47.5 KB
  • 大小: 38 KB
  • 大小: 50.7 KB
  • 大小: 70 KB
  • 大小: 74.1 KB
0
0
分享到:
评论

相关推荐

    spring security 参考手册中文版

    28.2.2 Java EE容器认证 220 29. LDAP认证 220 29.1概述 220 29.2在Spring Security中使用LDAP 221 29.3配置LDAP服务器 221 29.3.1使用嵌入式测试服务器 222 29.3.2使用绑定认证 222 29.3.3加载权限 223 29.4实现类 ...

    SpringSecurity 3.0.1.RELEASE.CHM

    2.4.1. &lt;global-method-security&gt;元素 2.4.1.1. 使用protect-pointcut添加安全切点 2.5. 默认的AccessDecisionManager 2.5.1. 自定义AccessDecisionManager 2.6. 验证管理器和命名空间 3. 示例程序 3.1. ...

    Spring Security 中文教程.pdf

    2.4.1. &lt;global-method-security&gt; 元素 2.4.1.1. 使用protect-pointcut 添加安全切点 2.5. 默认的AccessDecisionManager 2.5.1. 自定义AccessDecisionManager 2.6. 验证管理器和命名空间 3. 示例程序 ...

    Spring Security-3.0.1中文官方文档(翻译版)

    2.4.1. &lt;global-method-security&gt; 元素 2.4.1.1. 使用protect-pointcut 添加安全切点 2.5. 默认的AccessDecisionManager 2.5.1. 自定义AccessDecisionManager 2.6. 验证管理器和命名空间 3. 示例程序 ...

    JSP高级编程

    高级JSP技术&lt;br&gt;第8章 JSP开发平台的搭建:J2EE &lt;br&gt;8.1 J2SDKEE的安装和使用 &lt;br&gt;8.1.1 软硬件的支持 &lt;br&gt;8.1.2 安装 &lt;br&gt;8.2 J2SDKEE的配置 &lt;br&gt;8.2.1 JDBC的配置 &lt;br&gt;8.2.2 事务处理 &lt;br&gt;8.2.3 服务的端口号 &lt;br&gt;...

    lanlan2017#JavaReadingNotes#第12章 Java内存模型与线程 12.1 概述1

    title: 第12章 Java内存模型与线程 12.1 概述- 7 深入理解Java虛拟机:JVM高级特性与最佳实践(第3版)- 5第五部分 高效并发- 第1

    J2EE面试题

    %&gt; &lt;head&gt;&lt;/head&gt; &lt;body&gt; &lt;script&gt; function display(){  if(i == 1) { alert("Is 1"); } else if(i==2) {  alert("Is 2"); }else{ alert("Is other"); } } display (); &lt;/script&gt; &lt;/body&gt; &lt;/html&gt; a) ...

    JVM入门实战/arthas实战/垃圾回收算法/垃圾回收器/jvm内存模型分析

    第二节:JVM内存模型 1.1 概念 1.2 JVM内存模型 1.3 Heap堆内存模型 第三节:定位垃圾对象的依据 1.1 引用计数法 1.2 可达性算法 第四节:垃圾回收算法 1.1标记清除算法 1.2复制算法 1.3 标记整理(标记压缩)...

    java8集合源码分析-geektime-java-week-training-camp:极客时间-Java每周训练营

    内存模型、JVM 启动参数详解; JDK 内置命令行工具、JDK 内置图形界面工具、JDWP 简介、JMX 与相关工具; 常见的 JVM GC 算法(Parallel GC/CMS GC/G1 GC)基本原理和特点; 新一代 GC 算法(Java11 ZGC/Java12 ...

    OpenAI的Whisper模型

    该模型的整个高级实现包含在whirsper.h和whirsper.cpp中。其余代码是ggml机器学习库的一部分。 拥有这种轻量级的模型实现可以很容易地将其集成到不同的平台和应用程序中。举个例子,这里有一段在iPhone 13设备上...

    java开源包10

    JCaptcha4Struts2 是一个 Struts2的插件,用来增加验证码的支持,使用时只需要用一个 JSP 标签 (&lt;jcaptcha:image label="Type the text "/&gt; ) 即可,直接在 struts.xml 中进行配置,使用强大的 JCaptcha来生成验证码...

    JAVA上百实例源码以及开源项目

    笔者当初为了学习JAVA,收集了很多经典源码,源码难易程度分为初级、中级、高级等,详情看源码列表,需要的可以直接下载! 这些源码反映了那时那景笔者对未来的盲目,对代码的热情、执着,对IT的憧憬、向往!此时此...

    Java-Concurrency-Programming-Practice:学习 Java 并发编程

    学习并发编程的一些高级主题,如Java内存模型、JVM IO/NIO机制等。 在实践中学习: 在实践中学习:并发集合 在实践中学习:如何对并发应用程序进行测试。 实践学习:Java异步编程(Future、FutureTask、Guava....

    java面试笔试资料java笔试题大集合及答案题库java笔试题汇总资料188个合集.zip

    Java内存模型的历史变迁.docx Java在游戏服务器开发中的应用.docx java基础总结大全.txt Java开发与技术挑战——关于技术的技术思考.docx Java框架研发思考.docx Java程序员们最常犯的10个错误.docx java程序员的...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part2

    第1章 Java应用分层架构及软件模型  1.1 应用程序的分层体系结构  1.1.1 区分物理层和逻辑层  1.1.2 软件层的特征  1.1.3 软件分层的优点  1.1.4 软件分层的缺点  1.1.5 Java应用的持久化层  1.2 软件的模型 ...

    java面试笔试题库java软件设计java笔试题大集合及答案文档资料合集300MB.zip

    Java内存模型的历史变迁.docx Java在游戏服务器开发中的应用.docx java基础总结大全.txt Java开发与技术挑战——关于技术的技术思考.docx Java框架研发思考.docx Java程序员们最常犯的10个错误.docx java程序员的...

    java面试笔试题库java学习笔记开发教程互联网公司面试资料大全合集.zip

    Java内存模型的历史变迁.docx Java在游戏服务器开发中的应用.docx java基础总结大全.txt Java开发与技术挑战——关于技术的技术思考.docx Java框架研发思考.docx Java程序员们最常犯的10个错误.docx java程序员的...

    Java初级开发面试题

    Java虚拟机:Java内存模型、垃圾回收、类加载机制等。 常见的Java框架:Spring、Hibernate、Mybatis等。 Java设计模式:单例模式、工厂模式、代理模式等。 网络编程:TCP/IP、HTTP、Web Services等。 数据库相关...

    JAVA上百实例源码以及开源项目源代码

     基于JAVA的UDP服务器模型源代码,内含UDP服务器端模型和UDP客户端模型两个小程序,向JAVA初学者演示UDP C/S结构的原理。 简单聊天软件CS模式 2个目标文件 一个简单的CS模式的聊天软件,用socket实现,比较简单。 ...

Global site tag (gtag.js) - Google Analytics