java虚拟机规范中试图定义一种java内存模型(JMM)来屏蔽掉各种硬件和操作系统内存访问差异,以实现让java程序在各种平台都能打到一致的内存访问效果.所以java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层实现细节.注意,这里的变量是包括了实例字段,众泰字段,构成数组对象的元素,但不包括局部变量和方法参数,因为后者是线程私有的,不会被共享.

1.主内存和工作内存

  JMM规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存.工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存的变量,工作内存是线程私有的.这里指的主内存和工作内存,和java的虚拟机的内存划分不是同一个层次的划分,两者不对等,如果非要对等起来,主内存应该对于java堆中对象的数据区域,工作内存对于线程私有的虚拟机栈的一部分.

2.主内存与工作内存的交互

  主内存与工作内存之间如何交互,java内存模型定义了以下8个操作,每个操作都是原子的,不可再分的(对于double,long可能有例外,其在有些平台上会以32位为单位读取)

  1) lock:作用于主内存中的变量,它将一个变量标志为一个线程独占的状态。

  2) unlock:作用于主内存中的变量,解除变量的锁定状态,被解除锁定状态的变量才能被其他线程锁定。

  3) read:作用于主内存中的变量,它把一个变量的值从主内存中传递到工作内存,以便进行下一步的load操作。

  4) load:作用于工作内存中的变量,它把read操作传递来的变量值放到工作内存中的变量副本中。

  5) use:作用于工作内存中的变量,这个操作把变量副本中的值传递给执行引擎。当执行需要使用到变量值的字节码指令的时候就会执行这个操作。

  6) assign:作用于工作内存中的变量,接收执行引擎传递过来的值,将其赋给工作内存中的变量。当执行赋值的字节码指令的时候就会执行这个操作。

  7) store:作用于工作内存中的变量,它把工作内存中的值传递到主内存中来,以便进行下一步write操作。

  8) write:作用于主内存中的变量,它把store传递过来的值放到主内存的变量中。

  在将变量从主内存读取到工作内存中,必须顺序执行read、load;要将变量从工作内存同步回主内存中,必须顺序执行store、write。并且这8种操作必须遵循以下规则: 
  1) 不允许read和load、store和write操作之一单独出现。即不允许一个变量从主内存被读取了,但是工作内存不接受,或者从工作内存回写了但是主内存不接受。

  2) 不允许一个线程丢弃它最近的一个assign操作,即变量在工作内存被更改后必须同步改更改回主内存。

  3) 工作内存中的变量在没有执行过assign操作时,不允许无意义的同步回主内存。

  4) 在执行use前必须已执行load,在执行store前必须已执行assign。

  5) 一个变量在同一时刻只允许一个线程对其执行lock操作,一个线程可以对同一个变量执行多次lock,但必须执行相同次数的unlock操作才可解锁。

  6) 一个线程在lock一个变量的时候,将会清空工作内存中的此变量的值,执行引擎在use前必须重新read和load。

  7) 线程不允许unlock其他线程的lock操作。并且unlock操作必须是在本线程的lock操作之后。

  8) 在执行unlock之前,必须首先执行了store和write操作。

  对于普通变量来说,他的读写操作如下:

  变量读:

  1) 进入同步块时,同步块中读变量的值时,将会从主存中刷新到工作区,读到最新值
  2) 读volatile变量时,将会从主存中刷新到工作区,读到最新值;
  3) 读普通变量时,如果跟随在读volatile变量之后,将会从主存刷新到工作区,读到最新值
  4) 读final变量时,如果未初始化完成,则将等待final变量完成初始化,并获取主存中的最新值
  5) 新建线程中,变量的值将会从主存刷新到工作区,读到最新值
  变量写:
  1) 退出同步块时,同步块中变量的赋值被强制刷新到主存;
  2) volatile变量的赋值,被强制刷新到主存,时机在读volatile变量之前;
  3) 普通变量随同volatile变量,在同一个线程中的赋值,将跟随volatile变量被刷新到主存;
  4) final变量的赋值,在读final变量之前将被强制刷新到主存;
  5) 单独的普通变量的赋值,将在线程结束之前被刷新到主存;

3.volatitle变量的特殊规则

  volatile是java虚拟机提供的最轻量级的同步机制,volatile变量具有两个特性:一个是保证此变量对所有线程的可见性,即如果该变量的值被修改,这个新值对于其他线程来说是立即得知的.另一个是volatile会阻止指令重排序.由于volatile变量第一个特性,导致很多人认为volatile变量的是绝对的线程安全的,这个想法是有问题的,比如下面这个代码

 private static volatile int a=0;

     private static void increase(){
a++;
} public static void main(String[] args) {
Thread[] threads=new Thread[20];
for(int i=0;i<20;i++){
threads[i]=new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10000;i++)
increase();
}
});
threads[i].start();
}
while(Thread.activeCount()>1)
Thread.yield();
System.out.println(a);
}

  这里开启了20个线程,每个线程对a变量执行10000次加一操作,如果volatile变量是绝对的线程安全的,那么这个程序输出的结果必然为200000,但是这个程序输出的结果基本上是低于200000的.原因是,实现volatile变量的可见性的方法实际上是线程每次读这个变量的时候都会从主内存中读取这个值,每次写这个变量时写完会强制刷新到主内存中.在一个线程读取了这个值之后,另一个线程修改了主内存中的值,那么前一个线程的值就变成了过期的值,所以结果会出现误差.

  由于volatile变量只能保证可见性,所以在一些情况下还是需要加锁来保证原子性,在以下情况下能够不加锁使用volatile变量来保证原子性:

  1) 运算结果并不依赖变量当前的值,或者确保只有单个线程可以更新变量的值.

  2) 变量不需要与其他的状态变量共同参与不变约束

4.java内存模型的特征

  1) 原子性:java内存模型直接保证原子性变量操作的包括read,load,assign,use,store,write,大致的可以认为基本数据类型的读写是具备原子性的(double跟long除外,开头已经说了)

实现大范围的原子性可使用lock与unlock操作.

  2) 可见性:当一个线程修改了共享变量的值其他线程能够立即得知这个修改,变量如何实现可见性前面已经提到.

  3) 有序性:java程序中天然的有序性可以总结为:如果本线程内观察,所有操作都是有序的,如果一个线程中观察另一个线程,所有操作都是无序的.

5.先行先发生原则

  先行发生原则--是判断是否存在数据竞争、线程是否安全的主要依据。

  先行发生是Java内存模型中定义的两项操作之间的偏序关系。如果说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响被操作B察觉。

  下面是JAVA内存模型下一些天然的先行发生关系,不需要热河同步器协助就已经存在,虚拟机可以对他门进行重排序.

  1、程序次序规则:在一个线程内,书写在前面的代码先行发生于后面的。确切地说应该是,按照程序的控制流顺序,因为存在一些分支结构。

  2、Volatile变量规则:对一个volatile修饰的变量,对他的写操作先行发生于读操作.

  3、线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作.

  4、线程终止规则:线程的所有操作都先行发生于对此线程的终止检测.

  5、线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码所检测到的中断事件.

  6、对象终止规则:一个对象的初始化完成(构造函数之行结束)先行发生于发的finilize()方法的开始.

  7、传递性:A先行发生B,B先行发生C,那么,A先行发生C.

  8、管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作.

理解JVM之java内存模型的更多相关文章

  1. 深入理解JVM(6)——Java内存模型和线程

    Java虚拟机规范中定义了Java内存模型(Java Memory Model,JMM)用来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果(“即Ja ...

  2. 【JVM】Java内存模型

    原文:多线程之Java内存模型(JMM)(一) 概述 多任务和高并发是衡量一台计算机处理器的能力重要指标之一.一般衡量一个服务器性能的高低好坏,使用每秒事务处理数(Transactions Per S ...

  3. jvm(12)-java内存模型与线程

    [0]README 0.1)本文部分文字描述转自“深入理解jvm”,旨在学习“java内存模型与线程” 的基础知识:   [1]概述 1)并发处理的广泛应用是使得 Amdahl 定律代替摩尔定律称为计 ...

  4. 深入理解JMM(Java内存模型) --(三)顺序一致性

    数据竞争与顺序一致性保证 当程序未正确同步时,就会存在数据竞争.Java内存模型规范对数据竞争的定义如下: 在一个线程中写一个变量, 在另一个线程读同一个变量, 而且写和读没有通过同步来排序. 当代码 ...

  5. JVM总结-java内存模型

    我们先来看一个反常识的例子. int a=0, b=0; public void method1() { int r2 = a; b = 1; } public void method2() { in ...

  6. 深入理解JMM(Java内存模型) --(一)

    并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信 ...

  7. 深入理解JMM(Java内存模型) --(四)volatile

    volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这 ...

  8. JVM(7) Java内存模型与线程

    衡量一个服务性能的高低好坏,每秒事务处理数(Transactions Per Second,TPS)是最重要的指标之一,它代表着一秒内服务端平均能响应的请求总数,而 TPS 值与程序的并发能力又有非常 ...

  9. 深入理解JMM(Java内存模型) --(六)final

    与前面介绍的锁和volatile相比较,对final域的读和写更像是普通的变量访问.对于final域,编译器和处理器要遵守两个重排序规则: 在构造函数内对一个final域的写入,与随后把这个被构造对象 ...

随机推荐

  1. mobile crane 1

    void MobileCrane::rotateRope2() { //double r_angle1 = rotateRope + rorate3; //std::cout << &qu ...

  2. 常用OID(SNMP)

    系统参数(1.3.6.1.2.1.1) OID 描述 备注 请求方式 .1.3.6.1.2.1.1.1.0 获取系统基本信息 SysDesc GET .1.3.6.1.2.1.1.3.0 监控时间 s ...

  3. Python - Django - ORM 常用字段

    AutoField: int 自增列,必须填入参数 primary_key=True 如果没有写 AutoField,则会自动创建一个列名为 id 的列 from django.db import m ...

  4. 微信小程序技巧记录

    1.直接在app.json中添加pages,会自动按照路径生成page目录文件: 2.动态修改样式: /** * 页面的初始数据 */ data: { authorInfo: [], article: ...

  5. 【Leetcode_easy】1051. Height Checker

    problem 1051. Height Checker solution class Solution { public: int heightChecker(vector<int>&a ...

  6. unsupported media type 415

    jquery  ajax请求报错解决方案: 两点: data: JSON.stringify(obj), contentType:"application/json", 完整pos ...

  7. mycat搭建环境

    macos完全卸载mysql: https://blog.csdn.net/u012721519/article/details/55002626 踩过的坑: mycat1.6不支持单库分表; 最少要 ...

  8. Kubernetes环境部署

    简介 Kubernetes 是一个开源系统,用于容器化应用的自动部署.扩缩和管理.它将构成应用的容器按逻辑单位进行分组以便于管理和发现.   配置镜像源 Debian / Ubuntu apt-get ...

  9. BGP 实验

    一.环境准备 1. 软件:GNS3 2. 路由:c7200 二.实验操作 实验要求: 1. 掌握 BGP 的基本配置方法. 2. 掌握如何查看 BGP 的各种配置信息. 3. 掌握基于回环口的 BGP ...

  10. Model 的使用

    1.   设计数据结构 问题表Question:作用存放问题 id 主键 自增 question_text 题目 varchar120 created 创建时间 datetime 选项表Choice: ...