线程通信和线程同步
并发编程的两个核心问题是线程通信和线程同步,其中线程通信指线程之间以何种机制交换信息。常见的通信机制有两种:共享内存(线程之间共享公共状态,通过读-写公共状态来隐式通信)、消息传递(线程之间通过发送信息来显示通信),java采用共享内存的通行机制。同步指控制不同线程之间操作发生相对顺序(互斥)的机制。本篇主要从java的内存模型角度分析java底层如何保证内存可见性从而确保线程之间能够正常通行。
java 内存模型(JMM)
java线程之间的通讯由JMM控制,JMM决定一个线程对共享变量的写入何时对其他线程可见。
happens-before关系
定义:如果A 操作happends-before B操作,那么A操作的结果将对B操作可见。
判断方法
1、 一个线程中的每个操作 happens-before 于该线程中的任意后续操作。
2、对一个监视器的解锁 happens-before 于随后对该监视器(同一个锁)的加锁。
3、对一个监视器的解锁 happens-before 于随后对该监视器(同一个锁)的加锁。
4、如果A happens-before B 且 B happends-before C ,那么Ahappens-before C。
重排序
重排序分编译器重排序和处理器重排序,编译器重排序是指编译器在不改变单线程程序语义的情况下,可以改变语句的执行顺序,达到优化的目的。处理器重排序是指如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序,达到指令并行执行的目的。
重排序引发多线程的内存可见性问题,JMM会禁止特定类型的编译器重排序,同时在java编译器在生成的指令序列的适当位置插入内存屏障以此来禁止特定类型的处理器从排序。
顺序一致性内存模型对比JMM
顺序一致性模型是一个理想的参考模型,提供了极强的内存可见性保证,JMM参考这个模型。
在顺序一致性模型中,一个线程中的所有操作必须按照程序的流程来执行,不管同步与否,所有线程只能看到一个单一的操作执行顺序,JMM中无此保证,JMM对于同步程序有顺序一致性的效果,未同步程序在JMM中不但整体的执行顺序是无序的,而且所有线程看到的操作顺序也可能不一致,例如:线程A 执行 a=10操作(将值写到本地缓存),在将其刷新到主存之前,此操作只对线程A可见,在其他线程看来,因为看不到a的新值,所以就以为线程A并没有执行此操作。JMM对未同步的程序只提供最小安全性,线程执行时读取的值要么是某个线程写入的值,要么是默认值(0,null,false),不会出现一些无中生有的值。
顺序一致性模型保证对所有的内存读/写都具有原子性。JMM不保证对64位的long 、double变量的读/写操作具有原子性。从jsr-133内存模型开始(jdk5),仅仅只允许把一个64位long/double型变量的写操作拆分成两个32位的写操作来执行,任意的读操作在jsr-133中都具有原子性。
volatile
volatile具有可见性,对一个volatile变量的读,总是能看到对这个volatile变量最后的写入。同时volaile具有原子性,对任意单个volatile变量的读/写具有原子性(对long、double类型很有意义,对v++这种复合操作不具原子性)。
volatile 的写-读与锁的释放-获取有相同的内存效果(写==释放锁 ,读==获取锁)。
volatile 读/写的内存语义:当写一个volatile变量时,JMM会把对应的本地内存中的共享变量刷新到主存,实际是向接下来将要读取这个变量的某个线程发出消息。当读一个voltaile变量时,JMM会把该线程对应的本地缓存置为无效,线程接下来将从主存中读取共享变量。实际是接收到了之前某个线程发出的消息。因此volatile读/写实际是两个线程之间通过主存进行通信。
为了实现volatile的内存语义,JMM针对编译器制定了以下3条重排序规则,同时为了实现规则,JMM采取了如图1所示的内存屏障插入策略。
1、当第二个操作是volatile写时,不管第一个操作(volatile操作,普通操作)是什么,都不能重排序。
2、当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。
3、当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。
图1
final域
对于final域,编译器和处理器遵守两条重排序规则:1、在构造函数内对一个final域的写入与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作不能重排序。2、初次读一个包含final域的对象的引用与随后初次读这个final域,这两个操作之间不能重排序。JMM禁止把final域的写重排序到构造函数之外,由此可保证在对象引用为任意线程可见之前,对象final域已经被正确初始化了(普通域不具有这个保障),当然前提条件是final引用不能从构造函数内“溢出”(对象未构造完成之前就将其引用暴露出去)。编译器会在final域写之后,构造函数返回之前插入StoreStore屏障,由此便确保了final域的写不会重排序到构造函数之外。通过为final域增加读/写重排序规则,可以为java程序员提供初始化安全保证:只要对象是正确初始化的(被构造对象的引用在s构造函数中没“溢出”),那么不需要同步就可以保证任意线程都能看到final域在构造函数中被初始化的值。
相关推荐
并发编程之深入理解JMM&并发三大特性
详细的讲述了并发、高并发、CPU Cache、CPU多级缓存、CPU多级缓存 - 缓存一致性(MESI)、CPU多级缓存-乱序执行优化、Java内存模型(Java Memory Model,JMM)、并发的优势和风险...等等图文并茂详解
囊括了Android面试中的java多线程知识,包括线程的基础、threadLoca|、并发编程中的锁 JMM synchronized关键字 以及部分垃圾回收机制
Java 并发编程硬核资料
书籍:如《Java并发编程实战》、《Concurrency in C++》等。 官方文档:不同编程语言的官方文档通常会提供关于并发编程的指南和最佳实践。 社区和论坛:如Stack Overflow、Reddit等,可以提供实际问题的帮助和讨论。
JMM(java Memory Model)存在的意义及对并发的处理 监视器锁/显示锁、可重入/独占/共享/自旋锁之间的区别与联系 常见各种死锁以及解决方法和思路 JDK中的J、U、C框架介绍(主要包括线程池,并发容器,并发工具类)
并发编程关键字
并发编程学习笔记,来源于 itmuch整理,是学习并发的不错的学习资料,有大神的个人感悟。介绍了一些概念,比如并发,高并发,并行,锁。
介绍了两个重要的并行性能评估定律, 以及 Java 内存模型 JMM。第2章介绍了 Java 并行程序开发的基础, 包括 Java 中 Thread 的基本使用方法等第3章介绍了 JDK 内部对并行程序开发的支持, 主要介绍 JUC (Java.util....
day18_等待唤醒、JMM、并发编程特性、volatile.pdf
Java内存模型详解JMM Java内存模型(Java Memory Model,JMM)是Java虚拟机(JVM)中的一种内存模型,它描述了程序中各个变量之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节...
Java内存模型,即:JMM。当程序执⾏并⾏操作时,如果对数据的访问和操作不加以控制,那么必 然会对程序的正确性造成破坏。因此,我们需要在深⼊了解并⾏机制的前提下,再定义⼀种规则, 来保证多个线程间可以有效地...
6java8/9/10与并发 01 6.1java8的函数式编程简介 02 6.2函数式编程基础 03 6.3一步一步走入函数式编程 04 6.5增强的future:completablefuture 05 6.6读写锁的改进:stampedlock 06 6.7原子类的增强 实战Java高并发...
超全的多线程与高并发的编程笔记,从JVM&JMM角度讲多线程,synchronized优化原理,AQS和线程池等等,需要的童鞋请自行下载!
java内存模型jmm
深入Java内存模型-JMM。。。。。。。。。。。。。。。。。。
深入理解并发可见性、有序性、原子性与JMM内存模型深入理解并发可见性、有序性、原子性与JMM内存模型深入理解并发可见性、有序性、原子性与JMM内存模型深入理解并发可见性、有序性、原子性与JMM内存模型深入理解并发...
有的并发处理都有排队等候,唤醒,执行至少三个这样的步骤.所以并发肯定是宏观概念,在微观上他们都是序列被处理的,只不过资源不会在某一个上被阻塞(一般是通过时间片轮转),所以在宏观上看多个几乎同时到达的请求...
c#、java、php等多语言解决方案源代码 Wafer - 快速构建具备弹性能力的微信小程序 https://github.com/tencentyun/wafer 重要: 1.第二步,可以在5分钟内实现; 2.成本3元(腾讯云支持微信小程序2017年推广期间,3...
为智能手机安装java支持,使手机可以安装使用java应用程序如游戏等