并发编程模型

  • 1.两个关键问题

  • 1)线程之间如何通信

  • 共享内存

    程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信

  • 消息传递

    程之间没有公共状态,线程之间必须通过发送消息来显式进行通信

  • 2)线程之间如何同步

  • 线程之间没有公共状态,线程之间必须通过发送消息来显式进行通信

总结:Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。

  • 2.抽象结构

  • 1)本地内存

  • 每个线程都有一个私有的本地内存(LocalMemory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化

  • 2)主内存

  • 线程之间的共享变量存储在主内存

附图:

注:所有实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享(本章用“共享变量”这个术语代指实例域,静态域和数组元素)。局部变量(Local Variables),方法定义参数(Java语法规范称之为Formal Method Parameters)和异常处理器参数(Exception HandlerParameters)不会在线程之间共享,它们不会有内存可⻅性问题,也不受内存模型的影响。


  • 3.重排序


  • 定义:重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

  • 1) 3种类型

  • 编译器优化的重排序(处理器重排序)
  • 编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
  • 指令级并行的重排序(处理器重排序)

现代处理器采用了指令级并行技术(Instruction-Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序

内存系统的重排序

由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行

  • 2)数据依赖性


  • 说明:如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。

  • 存在三种类型(只要重排两个操作执行顺序,结果便会被改变)

    • 写后读:写一个变量之后,再读这个变量
    • 写后写:写一个变量之后,在写这个变量
    • 读后写:读一个变量之后,再写这个变量
  • 3)as-if-serial语义

  • 说明:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。

    • 顺序规则:A happens-before B,B happens-before C,happens-before C

      那么实际执行是B可以排在A前,JMM允许这种排序。
  • 4)对多线程的影响

  • 对于存证控制依赖的操作重排序,可能会改变程序的执行结果。
  • 5)DCL问题(double check lock)

    public class DoubleCheckedLocking { // 1
private static Instance instance; // 2 public static Instance getInstance() { // 3
if (instance == null) { // 4:第一次检查
synchronized (DoubleCheckedLocking.class) { // 5:加锁
if (instance == null) // 6:第二次检查
instance = new Instance(); // 7:问题的根源出在这里
} // 8
} // 9
return instance; // 10
} // 11
}
  • 那么问题就出现在线程执行到第4行,代码读取到instance不为null时,instance引用的对象有可能还

    没有完成初始化

    • __根源:
    memory = allocate();  //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory;   //3:设置instance指向刚分配的内存地址
  • JMM允许上述命令的执行顺序调整为
    memory = allocate();  //1:分配对象的内存空间
instance = memory;   //3:设置instance指向刚分配的内存地址
//注意,此时对象还没有被初始化!
ctorInstance(memory); //2:初始化对象
  • 问题:为什么要调整这个顺序呢?
  • 原因:这个重排序在没有改变单线程程序执行结果的前提下,可以提高程序的执行性能。



  • 解决方案

    • 不允许2和3重排序([volatile]);
    • 允许2和3重排序,但不允许其他线程“看到”这个重排序。
  • 第一种基于volatile方案

    public class SafeDoubleCheckedLocking {
private volatile static Instance instance; public static Instance getInstance() {
if (instance == null) {
synchronized (SafeDoubleCheckedLocking.class) {
if (instance == null)
instance = new Instance(); // instance为volatile,现在没问题了
}
}
return instance;
}
}
  • 第二种基于类初始化方案
    public class InstanceFactory {
private static class InstanceHolder {
public static Instance instance = new Instance();
} public static Instance getInstance() {
return InstanceHolder.instance;  // 这里将导致InstanceHolder类被初始化,存在初始化锁,拿不到的线程会一直等待
}
}

  • 4.happens-before

happens-before是JMM最核心的概念

1) 关系的定义

  • 如果⼀个操作happens-before另⼀个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。[JMM对程序员的承诺]
  • 两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)[JMM对编译器和处理器重排序的约束原则]

2) 规则

  • 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
  • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
  • volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
  • 传递性:A happens-before B,且B happens-before C,那么A happens-before C。
  • start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
  • join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。

注意:两个操作之间具有happens-before关系,并不意味着前

一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操

作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第一个操

作之前(the first is visible to and ordered before the second)。

  • appens-before与JMM的关系

话外语:

  • as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变
  • as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度

【并发编程】- 内存模型(针对JSR-133内存模型)篇的更多相关文章

  1. Java并发编程(十四)Java内存模型

    1.共享内存和消息传递 线程之间的通信机制有两种:共享内存和消息传递:在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程 ...

  2. Java并发编程里的volatile。Java内存模型核CPU内存架构的对应关系

    CPU内存架构:https://www.jianshu.com/p/3d1eb589b48e Java内存模型:https://www.jianshu.com/p/27a9003c33f4 多线程下的 ...

  3. Java并发编程实战 第16章 Java内存模型

    什么是内存模型 JMM(Java内存模型)规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对其他线程可见. JMM为程序中所有的操作定义了一个偏序关系,称为Happens-Be ...

  4. java并发编程实战《二》java内存模型

    Java解决可见性和有序性问题:Java内存模型 什么是 Java 内存模型? Java 内存模型是个很复杂的规范,可以从不同的视角来解读,站在我们这些程序员的视角,本质上可以理解为, Java 内存 ...

  5. Java并发编程(二):JAVA内存模型与同步规则

    一.Java内存模型(JMM) 它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式.一个线程如何和何时能看到其他线程共享变量的值,以及在 ...

  6. 转: 【Java并发编程】之十八:第五篇中volatile意外问题的正确分析解答(含代码)

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17382679 在<Java并发编程学习笔记之五:volatile变量修饰符-意料之外 ...

  7. 《java并发编程实战》读书笔记13--Java内存模型,重排序,Happens-Before

    第16章 Java内存模型 终于看到这本书的最后一章了,嘿嘿,以后把这本书的英文版再翻翻.这本书中尽可能回避了java内存模型(JMM)的底层细节,而将重点放在一些高层设计问题,例如安全发布,同步策略 ...

  8. python并发编程-进程间通信-Queue队列使用-生产者消费者模型-线程理论-创建及对象属性方法-线程互斥锁-守护线程-02

    目录 进程补充 进程通信前言 Queue队列的基本使用 通过Queue队列实现进程间通信(IPC机制) 生产者消费者模型 以做包子买包子为例实现当包子卖完了停止消费行为 线程 什么是线程 为什么要有线 ...

  9. 转: 【Java并发编程】之十三:生产者—消费者模型(含代码)

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17249321 生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一 ...

  10. 【Java并发编程】之十三:生产者—消费者模型

    生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据. ​ 这里实现如下情况的生产--消费模型: ​ 生产者不断交替地生产两组 ...

随机推荐

  1. 第九章 Python文件操作

    前一阵子写类相关的内容,把老猿写得心都累了,本来准备继续介绍一些类相关的知识的,如闭包.装饰器.描述符.枚举类.异常等,现在实在不想继续,以后再开章节吧.本章弄点开胃的小菜提提神,介绍Python中文 ...

  2. Python中文文件处理中涉及的字符编码及字符集

    在现在的互联网,字符编码是互联网信息交互的一个重要基础,各种语言都有支持信息编码的机制,Python也不例外.Python除了字符编码之外,对于字节码和字符串两种类型有严格区分,字符串是本地可以读取的 ...

  3. Scrum 冲刺第一天

    一.团队信息 1.团队名称 挑战极限队 2.团队成员 张博愉(3118005074) 张润柏(3118005075) 郑堉涵(3118005077) 周伟建(3118005079) 林梓琦(31180 ...

  4. element ui只输入数字校验

    注意:圈起来的两个地方,刚开始忘记写type='number'了,导致可以输入'123abc'这样的,之后加上了就OK了

  5. 深入解析volatile关键字

    前言 很高兴遇见你~ 欢迎阅读我的文章. volatile关键字在Java多线程编程编程中起的作用是很大的,合理使用可以减少很多的线程安全问题.但其实可以发现使用这个关键字的开发者其实很少,包括我自己 ...

  6. 网络层-network layer(下):网络互连、子网掩码计算方法、Ipv4报头解析

    第五章 网络层-Network Layer(下) 上一章讲了网络层的任务.提供的两种服务.五个重要的路由算法.以及网络层的拥塞控制和服务质量问题.这一部分主要讲一讲网络互连问题和Internet的网络 ...

  7. Codeforces Edu Round 53 A-D

    A. Diverse Substring 找普遍性(特殊解即可). 最简单的便是存在一个区间\([i, i + 1] (1 <= i < n)\),且$str[i] $ $ != str[ ...

  8. (原创)nginx反向代理上网,使docker可以拉取镜像

    转载请注明出处:饭饭博客:https://www.cnblogs.com/zzf0305/p/9602107.html 摘语:参考了N多大侠的资料,终于将自己的环境配置起来了.站在众多巨人的肩膀进行总 ...

  9. 2. 使用Shell能做什么

    批处理 在批处理的过程中,能够实现脚步自动化,比GUI自动化速度高效 日常工作场景 服务端测试 移动端测试 持续集成与自动化部署,这是最最场景的场景,可以说离开了shell,持续集成和自动化部署也会遇 ...

  10. ASP .Net Core 中间件的使用(一):搭建静态文件服务器/访问指定文件

    前言 随着Asp .Net Core的升级迭代,很多开发者都逐渐倾向于.net core开发. .net core是一个跨平台的应用程序,可以在windows.Linux.macOS系统上进行开发和部 ...