Java内存模型

1. 内存模型的基础

  • 编发编程模型的两个关键问题
    1) 线程之间的通信
    通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信有两种机制:共享内存和消息传递。
    在共享内存的并发模型中,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。Java并发采用的是共享内存模型。
    在消息传递的并发模型里,线程之间没有公共的状态,线程之间必须通过发送消息来显式进行通信
    2) 线程之间的同步
    同步指程序中用于控制不同线程间操作发生相对顺序的机制。在共享内存并发模型里,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。在消息传递并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。
  • Java内存模型的抽象结构
    Java线程之间的通信由Java内存模型 JMM 控制,JMM决定了一个线程对共享变量的写入何时对另一个线程可见。
    抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。
  • 从源代码到指令序列的重排序
    在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。
    1) 编译器优化的重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
    2) 指令级并行的重排序:现代处理器采用指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序
    3) 内存系统的重排序:由于处理器使用缓存和读/写缓冲区,使得加载和存储操作看上去可能是在乱序执行

2. 重排序

  • 数据依赖性
    如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时两个操作之间存在数据依赖性。
  • as-if-serial语义
    as-if-serial语义的意思是不管怎样的重排序,(单线程)程序的执行结果不能被改变。为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作作重排序,因为这种重排序会改变执行结果。
  • 重排序对多线程的影响
    在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果

3. 顺序一致性

  • 顺序一致性内存模型
    1) 一个线程中的所有操作必须按照程序的顺序来执行
    2)(不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见
  • 同步程序的顺序一致性
    顺序一致性模型中,所有操作完全按照程序的顺序执行。而在JMM中,临界区内的代码可以重排序(JMM不允许临界区内的代码逸出到临界区外,这样会破坏监视器语义)。JMM会在退出和进入临界区做特别处理,使得这两个时间点具有与顺序一致性相同的内存视图。
  • 未同步程序的执行差异
    1)JMM不能保证单线程内的操作会按程序的顺序执行
    2)JMM不能保证所有线程能看到一致的操作执行顺序
    3)JMM不能保证对64位的long和double型变量的写操作具有原子性

4. volatile的内存语义

  • volatile的特性
    1)可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入
    2)原子性:对任意单个volatile变量的读/写具有原子性,但类似i++这种复合操作不具有原子性
  • volatile 写-读的内存语义
    volatile写内存语义:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存
    volatile读内存语义:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程将从主内存中读取共享变量
  • volatile内存语义的实现
    为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。基于保守策略的JMM内存屏障插入策略:
    1)在每个volatile写操作前面插入StoreStore屏障
    2)在每个volatile写操作后面插入StoreLoad屏障
    3)在每个volatile读操作后面插入LoadLoad屏障
    4)在每个volatile读操作后面插入LoadStore屏障

5. 锁的内存语义

  • 公平锁与非公平锁的内存语义
    1)公平锁和非公平锁释放时,最后都要写一个volatile变量state
    2)公平锁获取时,首先会去读volatile变量
    3)非公平锁获取时,首先会用CAS更新volatile变量,这个操作同时具有volatile读和写的内存语义
  • CAS: 如果当前状态值等于预期状态值,则以原子方式将同步状态设置为给定的更新值。
  • Java线程之间的通信4种方式
    1)A线程写volatile变量,随后B线程读这个volatile变量
    2)A线程写volatile变量,随后B线程CAS更新这个volatile变量
    3)A线程用CAS更新这个变量,随后B线程用CAS更新这个volatile变量
    4)A线程用CAS更新这个变量,随后B线程读这个volatile变量
  • concurrent包通用化实现模式
    首先,声明共享变量为volatile
    然后,使用CAS的原子条件实现线程之间的同步
    同时,配合volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信
    AQS,非阻塞数据结构,原子变量类这些concurrent包中基础类都是使用这种模式实现,而concurrent包中高层的类依赖于基础类来实现。

6.final域的内存语义

  • final域的重排序规则
    1)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序
    2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序
  • 写final域的重排序规则
    1)JMM禁止编译器把final域的写重排序到构造函数外
    2)编译器会在final域的写之后,构造函数return之前,插入StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造函数外
  • 读final域的重排序规则
    在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作。编译器会在读final域操作的前面插入LoadLoad屏障
  • final域为引用类型
    约束:在构造函数内对一个final引用对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序

7. happens-before

  • JMM设计
    JMM向程序员提供的happens-before 规则满足程序员的需求,happens-before规则不但简单易懂,而且向程序员提供足够强的内存可见性保证。
  • happens-before规则
    1)程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作
    2)监视器规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
    3)volatile变量规则:对一个volatile域的写,happens-before于后续任意对这个volatile域的读
    4)传递性:A happens-before B,且B happens-before C,那么 A happens-before C
    5)start()规则:如果线程A执行操作ThreadB.start()启动线程B,那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作
    6)join()规则:如果线程A执行ThreadB.join()并成功返回,那么线程B中的任意操作happens-before 于线程A从ThreadB.join()操作成功返回
    7)程序中断规则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。
    对象
    8)finalize规则:一个对象的初始化完成(构造函数执行结束)先行于发生它的finalize()方法的开始。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值