Java内存模型 读笔

Java内存模型的基础

并发编程模型的两个关键问题:

  • 线程之间的通信机制:
    • 共享内存:线程之间共享程序的公共状态,通过读-写内存中的公共状态进行隐式通信。
    • 信息传递:线程之间没有公共状态,通过发送消息显式进行通信。
  • 线程之间的同步机制:用于控制不同线程操作发生的相对顺序的机制。
    • 共享内存模型中,同步是显式进行,必须显式指定方法或代码段需要的互斥执行。
    • 消息传递模型中,消息的发送必须在消息的接受前,则同步是隐式进行的。

Java内存模型的抽象结构
Java中,所有实例域,静态域和数组元素都存储在堆内存中,堆内存在线程之间共享。
Java线程之间的通信由Java内存模型(JMM)控制,其定义了线程和主存之间的抽象关系:线程之间共享变量存储在主内存(Main Memory),每个线程都有一个本地内存(Local Memory,抽象概念,实际不存在),存储了线程读写共享变量的副本。
这里写图片描述
AB之间通信步骤:
1.线程A将本地内存A中更新的共享变量刷新到主内存中。
2.线程B到主内存中读取线程A已经更新的共享变量。
这里写图片描述

从源代码到指令序列的重排序:
重排序的种类:

  • 编译器的重排序:在不改变单线程程序语言情况下,编译器重新安排语句的执行顺序。
  • 指令级并行的重排序:在不存在数据依赖性情况下,处理器可以改变语句对于机器指令的执行顺序。
  • 内存系统的重排序:由于处理器使用缓存和读写缓冲区,使加载和存储操作可能看上去是乱序。

由于重排序可能会与多线程程序造成内存可见性问题,JMM禁止特定类型的编译器重排序,通过插入特定类型的内存屏障 (Memory Barriers)来禁止特定类型的处理器重排序。

happens-before:
通过happens-before概念描述操作之间的内存可见性。
happens-before规则:

  • 程序顺序规则:一个线程中的每一个操作,happens-before于该线程中的任意后续操作。
  • 监视器规则:对于一个锁的解锁,happens-before于随后对于中国锁的加锁。
  • volatile变量规则:对一个volatile域的鞋,happens-before于任意对这个volatile域的读。
  • 传递性:如果A happens-before B,且B happens-before C,则A happens-before C。

数据依赖性:
如果两个操作访问同一个变量,且这两个操作自己有一个为写操作,则两个操作自己存在数据依赖性。
为: 写后读,写后写,读后写三种。
编译器和处理器不会改变存在数据依赖性关系的两个操作之间的执行顺序。

as-if-serial语义:
不管怎么重排序,(单线程)程序的执行结果不能被改变。
在不改变结果的前提下,通过重排序,尽可能提高并行度。

顺序一致性:
如果程序是正确同步的,程序的执行将具体顺序一致性——程序的执行结果和该程序在顺序一致性模型中的执行结果相同。

  • 顺序一致性的内存模型:
    1.一个线程中的所有操作必须按照程序的顺序来执行。
    2.(不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。每一个操作都必须原子执行且立即对所有线程可见。

同步程序:执行结果与程序的顺序一致性模型中执行结果相同。顺序一致模型中,所有操作按照程序顺序串行执行;JMM中,临界区内代码可以重排序。
未同步程序:在JMM中不但整体的执行顺序是无序的,而且所有线程看到的操作顺序也可能不一致。JMM只提供最小安全性:线程执行读取到的值,是其他线程写入,或者是默认值。
在一些32位处理器上,若对64位数据写操作具有原子性,将有较大开销。所以Java语言鼓励但不强求JVM对于64位的long double的写操作具有原子性。JVM在该处理器上运行,可能将一个64位的long double变量拆分为两个32位的写操作执行,两个操作可能被分配到不同总线事务执行,即不具有原子性。

volatile的内存语义:
volatile的特性:

  • 可见性:对弈一个volatile变量的读,总是可以看到(任意线程)对这个volatile变量最后的写入。
  • 原子性:对任意单个volatile变量的读/写具有原子性,但是对于类似于volatile++这种复合操作不具有原子性。

volatile读/写的内存语义:

  • 读:当读取一个volatile变量时,JMM会把该线程对应本地内存设置为无效。线程接下来从主内存中读取共享变量。
  • 写:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。

volatile内存语义的实现:
对于编译器通过按照相应的volatile重排序规则表限制其重排序;对于处理器通过相应的内存屏障,限制其重排序。

锁的内存语义:
锁的释放和获取的内存语义:
锁让临界区互斥执行,让释放锁的线程向获取同一个锁的线程发送消息(在释放这个锁之前对共享变量所做的修改)。
- 释放:JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
- 获取:当读取一个volatile变量时,JMM会把该线程对应本地内存设置为无效。线程接下来从主内存中读取共享变量。

锁的内存语义的实现:
通过对于ReenteanLock的分析,得对于公平锁和非公平锁的内存实现通过对于获取锁的方式上有所不同,公平锁通过先读volatile变量state,非公平锁通过CAS(通过当前状态值等于预期值,以原子方式将同步状态设置为给定值,具有volatile读和写的内存语义)方式更新state变量。

总结:

  • 公平锁和非公平锁释放时,最后都要写一个volatile变量state。
  • 公平锁获取时,会先去读volatile变量。
  • 非公平锁获取时,首先会用CAS更新volatile变量,具有volatile读和写的内存语义。

锁释放-获取的内存语义有以下两种方式:

  • 利用volatile变量的写-读具有的内存语义。
  • 利用CAS所具有的volatile读和写的内存语义。

concurrent包的实现:
由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:

  • A线程写volatile变量,随后B线程读这个volatile变量。
  • A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
  • A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
  • A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。

则concurrent包具有一种通用化的实现方式:
1.声明共享变量为volatile;
2.使用CAS的原子条件更新来实现线程之间的同步;
3.配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值