【Java】内存模型

概述

C/C++等语言直接使用物理硬件和操作系统的内存, 因此在不同平台上运行会产生差异, 导致一些并发访问经常出错。

Java虚拟机规范中试图定义一种Java内存模型,主要目标是定义程序中各个变量的访问规则, 用来屏蔽掉各种硬件和操作系统间的内存访问差异, 让Java程序能够正常地在各个平台运行,并且避免并发的错误问题。

定义Java内存模型必须严谨, 让访问内存操作不能产生歧义, 又要必须给虚拟机更多的自由空间去利用硬件的各种特性(寄存器, 高速缓存和特有指令), 以便获取更好的执行速度。

JDK1.5发布后 Java内存模型已经成熟和完善起来。

主内存与工作内存

在这里插入图片描述
Java内存模型规定所以的变量都存储在主内存(可类比硬件中的内存),每条线程拥有自己的工作内存(可类比高速缓存), 工作内存保存了被该条线程所使用的,保存在主内存中对象的副本。

线程对变量的读写访问只能在工作内存中.

不同的线程无法访问其他线程的工作内存。

可以勉强将工作内存对应虚拟机栈, 主内存对应堆. 但并不是很准确. 因为二者属于不同层次的划分.

内存间的交互操作

Java内存模型定义了一下8种操作来实现工作内存和主内存之间的交互. 下面每种操作都是原子操作(double和long某些可能有特殊实现, 但目前使用的虚拟机并不影响其原子性)

  • lock(锁定) : 作用于主内存的变量, 把一个变量标识为一条线程独占的状态
  • unlock(解锁) : 作用与主内存的变量, 把一个处于锁定状态的变量释放出来, 释放后的变量才可以被其他线程锁定
  • read(读取) : 作用于主内存的变量, 它把一个变量的值从主内存传输到线程的工作内存中, 以便于随后的load动作
  • load(载入) : 作用于工作内存的变量, 它把read操作从主内存中得到的变量值放入工作内存的变量副本中
  • use(使用) : 作用域工作内存的变量, 它把工作内存中一个变量的值传递给执行引擎, 每当虚拟机遇到一个需要使用到变量的值的字节码指令时会执行这个操作
  • assign(赋值) : 作用于工作内存的变量, 它把一个从执行引擎接收到的值付给工作内存中的变量, 每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
  • store(存储) : 作用于工作内存的变量, 它把工作内存中一个变量的值传送到主内存中 , 以便随后的write使用
  • write(写入) : 作用于主内存的变量, 把store操作从工作内存汇总得到的 变量 值放入主内存的变量中

read和load 将变量从主内存复制到工作内存必须顺序执行, store和write也同理.
除此之外Java内存模型规定了在执行上述八种操作必须满足以下规则:

  • lock(锁定) : 作用于主内存的变量, 把一个变量标识为一条线程独占的状态
  • unlock(解锁) : 作用与主内存的变量, 把一个处于锁定状态的变量释放出来, 释放后的变量才可以被其他线程锁定
  • read(读取) : 作用于主内存的变量, 它把一个变量的值从主内存传输到线程的工作内存中, 以便于随后的load动作
  • load(载入) : 作用于工作内存的变量, 它把read操作从主内存中得到的变量值放入工作内存的变量副本中
  • use(使用) : 作用域工作内存的变量, 它把工作内存中一个变量的值传递给执行引擎, 每当虚拟机遇到一个需要使用到变量的值的字节码指令时会执行这个操作
  • assign(赋值) : 作用于工作内存的变量, 它把一个从执行引擎接收到的值付给工作内存中的变量, 每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
  • store(存储) : 作用于工作内存的变量, 它把工作内存中一个变量的值传送到主内存中 , 以便随后write使用
  • write(写入) : 作用于主内存的变量, 把store操作从工作内存汇总得到的 变量 值放入主内存的变量中

volatile变量的规则

一个变量定义为volatile后, 它将具备两种特性

  • 对所有线程的可见性
    当一个线程修改了该变量的值, 则新值对其他线程立即可见的(普通变量做不到). 即volatile变量在各个线程中是一致的.
    注: 具有volatile变量具有可见性但不表示它是安全的, 如果要保证其原子性还需要对其进行加锁操作.
  • 禁止指令重排序
    普通的变量在执行过程中仅会保证线程内为串行的语义, 在多线程并发环境下则不一定. 禁止指令重排序则会保证变量赋值顺序与代码中的顺序一致, 从而会避免一些并发中的错误.

原子性、可见性和有序性

Java内存模型围绕着并发过程中如何处理原子性、可见性和游戏性这三个特征来建立

  • 原子性
    Java内存模型保证原子性的操作包括 read 、 load、 assign、 use、store和write(long和double的例外情况几乎不太会发送), 所以基本类型的读和写都具有原子性, 但如果更大范围的原子性(如 i++, 先读再写) 则需要用加锁完成.

  • 可见性
    当一个线程修改了某个变量的值, 其他线程能够立即得知这个值
    Java内存模型通过变量修改后将值同步回主内存这种方式来实现可见性.
    普通变量和volatile都是如此,但 volatile的特殊规则保证了新值能立即同步到主内存, 以及每次使用前从主内存刷新,所以volatile变量保证了多线程操作时变量的可见性.
    java中的synchronized和final关键字也可以实现可见性.

  • 有序性
    本线程内所有的操作都是有序的, 不同线程之间的观察是无序的.
    volatile和synchronized都可以保证有序性.

先行先发生原则

该原则是判断数据是否存在竞争, 线程是否安全的主要依据, 可以通过几条规则解决并发下两个操作之间是否可能存在冲突的所有问题.

一下规则规定的两个操作的顺序性是得到保障的, 而不在下列的两个操作虚拟机可以对他们进行随意的重排序.

-程序次序规则 : 在一个线程内, 按照程序代码顺序, 书写在前面的操作先行发生在后面的操作(控制流顺序)

  • 管程锁定规则 : 一个unlock操作先行发生于后面对同一个锁的lock操作
  • volatile变量规则 : 对于一个volatile变量的写操作先行发生于后面对这个变量的读操作
  • 线程启动规则 : Thread对象的start()方法先行发生与对此线程的每一个动作
  • 线程终止规则, 线程中的所有操作都先行发生与此线程的终止检测, 我们可以通过Thread.join()方法结 束, Thread.isAlive()的返回值等手段检测到线程已经终止执行
  • 线程中断规则 : 对线程的interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  • 对象终结规则 : 一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始
  • 传递性 : 如果A操作先行发生于B, B操作线程发生于C, 则 A操作先行发生于C

参考:《深入理解Java虚拟机》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值