`
alchimie
  • 浏览: 19527 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

[深入理解Java虚拟机]读书笔记(2)Java内存模型

    博客分类:
  • JVM
阅读更多

 

       在现在计算机的硬件构架中,每个处理器都有自己的高速缓冲存储器,用来解决处理器的运算速度和内存之前几个数理级的差距。而各个高速缓存与主内存之间的数据一致性要求则是通过缓存一致性协议来保证的。而Java虚拟机也有自己的内存模型,并且与硬件的缓存操作具有很高的可比性:



 

 

1.Java内存模型

       规定了所有的变量都存储在主内存(Main Memory)中(与物理硬件的命名一致,可以类比,但此处仅是JVM内存的一部分),每个线程还有自己的工作内存(Working Memory,可与高速缓存类比),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝(并非完全拷贝,具体的实现有待查证...),线程对变量的所有操作(读取,赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile同样如此)。

       不同线程无法访问其它工作内存中的变量,线程间的变量的交互均需要通过主内存来完成。如上图。

 

1.1.内存间的交互操作

Java内存模型定义了以下8种操作来完成主内存和工作内存之间的交互:

lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占的状态。

unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其它线程锁定。

read(读取):作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。

load(载入):作用于工作内存的变量,把read操作从主内存中得到的变量值放入工作内存的变量副本中。

use(使用):作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。

assign(赋值):作用于工作内存的变量,把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量的赋值的字节码指令时执行这个操作。

store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write操作作使用。

write(写入):作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。

 

1.2.对于volatile型变量的特殊规则

       volatile有两个语义,其一是保证此变量对所有线程的可见性:对volatile变量的写操作会立刻反应到主内存中,读操作始终会读取主内存中的当前最新值。这里使用当前是因为,Java里的面运算并非原子操作,这会导致volatile变量的运算在并发下是不安全的。下面的代码来自[深入理解JVM虚拟机]的12-1:

 

/**
 * volatile变量自增运算测试
 * 
 * @author zzm
 */
public class VolatileTest {
	public static volatile int race = 0;

	public static void increase() {
		race++;
	}

	private static final int THREADS_COUNT = 20;

	public static void main(String[] args) {
		Thread[] threads = new Thread[THREADS_COUNT];
		for (int i = 0; i < THREADS_COUNT; 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(race);
	}
}
如果这端代码正确并发的吗,结果应该是200000,但经过多次运行可以看到,输出的结果小于200000。问题出现在自增“race++”上,自增不是原子操作,可以看到class文件中increase()是由此来4条字节码指令构成的(不算return)。从字节码层面上可以分析出并发失败的原因:当getstatic指令把race的值取到操作栈顶时,volatile关键字保证了race的值此时是正确的,但当执行iconst_1、iadd这些指令时,其它线程可能已经把race的值加大了,此时操作栈顶的值就成了过期的数据,所以putstatic指令执行后就有可能把过期的数据写入主面存中。
  public static void increase();
    0  getstatic chapter12.VolatileTest.race : int [13]
    3  iconst_1
    4  iadd
    5  putstatic chapter12.VolatileTest.race : int [13]
    8  return
      Line numbers:
        [pc: 0, line: 11]
        [pc: 8, line: 12]

 

volatile在下面两条规则以外,仍需通过加锁来保证原子性:

①运算结果不依赖于变量的当前值或能够确保只有单一的线程修该变量的值。

②变量不需要与其它的状态变量共同参与不变约束。

 

其二是禁止指令重排序优化,保证变量赋值操作的顺序与程序代码中的执行顺序一到。具体请参照[单例模式]②双重检查加锁(不推荐)的说明。

 

1.3.先行发生原则(happens-before)

      先行发生原则是Java内存模型中定义的两项操作之间的顺序关系,如果操作A先行发生于操作B,那么操作A产生的影响是能被操作B观察到的,“影响”包括修该了内存中共享变量的值,发送了消息,调用了方法等。它是判断数据是否存在竞争、线程是否安全的主要依据。

     

下面是Java内存模型下一些"天然的"先行发生关系,无须任何同步已经存在:

程序次序规则(Program Order Rule):在一个线程内,按照程序执行顺序,书写在前面的操作先行发生于书写在后面的操作(控制流顺序而不是程序代码顺序)。

线程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作(同一个锁,时间上的先后)。

volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读操作(时间上的先后)。

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

线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终此检测(可用Thread.isAlive()的返回值等检测线程是否已经终止)。

线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生(Thread.interrupted()方法检测)。

对象终结规则(Finalizer Rule):一个对象的初始化(构造函数执行结束)完成先行发生于它的finalize()方法的开始

传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那么操作A先行发生于操作C。

  • 大小: 29.2 KB
分享到:
评论

相关推荐

    学习深入理解Java虚拟机的前几章笔记

    包括jvm 的内存模型 对象的创建过程 垃圾回收算法 垃圾回收器 内存分配和回收策略

    java虚拟机知识点整理

    自己看书整理的 java虚拟机精品知识点 java内存区域与内存溢出处理 虚拟机栈和本地方法栈区别 对象定位访问 垃圾收集器GC管理 虚拟机GC垃圾回收收集算法(内存回收方法论) 虚拟机GC垃圾回收收集器(内存回收具体实现...

    免费分享 Java面试笔记 面试八股文 计算机网络基础

    JVM(Java虚拟机):Java内存管理详解、垃圾回收机制、垃圾回收器等;MySQL:基础知识、存储引擎、日志、SQL优化、数据索引、锁、事务、高可用实现等;Spring:IOC、AOP、声明式事务、MVC等;Redis:持久化过程、高...

    互联网Java面试训练营.rar

    13. 推荐收藏系列:一文理解JVM虚拟机(内存、垃圾回收、性能优化)解决面试中遇到问题 14. 2020年大厂Java面试前复习的正确姿势(800+面试题附答案解析) 15. 大白话聊聊Java并发面试问题之Java 8如何优化CAS性能...

    Java运行环境 Java SE Runtime Environment 8.0.172.x64官方多语言正式版

    Java SE Runtime Environment(运行时环境)包含了运行以 Java 编程语言编写的程序所必需的 Java 虚拟机、运行时类库和 Java 应用程序启动器。 Java 平台的安全性 一个由角色提供的安全信息的描述。适用于开发人员...

    JVM历史发展和内存回收笔记.rar

    虚拟机的历史版本和JAVA内存分配,未来的虚拟机技术, 运行时数据区域,方法的出入栈,栈上分配

    Java运行环境 Java SE Runtime Environment 8.0.172.x86官方多语言正式版

    Java SE Runtime Environment(运行时环境)包含了运行以 Java 编程语言编写的程序所必需的 Java 虚拟机、运行时类库和 Java 应用程序启动器。 Java 平台的安全性 一个由角色提供的安全信息的描述。适用于开发人员...

    Notes:This is a learning note | Java基础,JVM,源码,大数据,面经

    深入理解Java虚拟机 Java内存区域划分与对象新建过程 jvm垃圾收集机制与内存分配策略 jvm类加载机制 Java的内存模型 锁优化 Think In Java Java容器 Java并发 Java Concurrency in Practice 对象的共享 对象的组合 ...

    java8集合源码分析-Awesome-Java:真棒-Java

    Java虚拟机相关,内存模型,类加载机制,JVM性能解析等 零散的文章 数据结构与算法 算法的度量,基础数据结构,链表,二叉树,B树,图论,深度和广度优先算法,排序,查找等 设计模式 常用设计模式的Java语言描述 ...

    Scala学习笔记

    Scala运行于Java平台(Java虚拟机),并兼容现有的Java程序。它也能运行于CLDC配置的Java ME中。目前还有另一.NET平台的实现[2],不过该版本更新有些滞后。[3] Scala的编译模型(独立编译,动态类加载)与Java和C#...

    java8rt.jar源码-JVM:学习JVM

    2.java虚拟机栈:线程私有,虚拟机栈描述的是java方法执行的内存模型:每个方法在执行的时候都会创建一个栈帧,存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个 方法从调用到执行完成的过程中,就对应...

    美团和蚂蚁金服面试笔记.pdf

    Java内存模型 线程公有方法区:Java方法区和堆一样,方法区是一块所有线程共享的内存区域,他保存系统的 类信息。比如类的字段、方法、常量池等。方法区的大小决定系统可以保存多少个类。如果 系统定义太多的类,...

    java8集合源码分析-CS-Notes-Links:Java开发&计算机基础学习过程中的笔记梳理

    笔记内容大部分都是参考了网上的博客以及书籍,整理笔记的习惯也是刚刚养成的,从开始整理笔记后,发现自己对于做过的东西,能够随着笔记很快的回忆起来,遇到一些问题能够快速从笔记里找到答案也是比较舒服的。笔记...

    java多线程tcpsocketserver源码-netty-learning:网络学习

    java多线程tcp socket server源码@Netty 笔记 1. 内蒂 网络应用框架 特征 设计 支持多种传输类型 事件模型 高度可定制的线程模型 表现 高吞吐量,低延迟 更少的资源消耗 最小化内存拷贝 安全SSL/TLS 支持 建筑学 核 ...

    Eclipse开发分布式商城系统+完整视频代码及文档

    │ │ 深入理解Java内存模型.pdf │ │ │ └─课后资料 │ ├─笔记 │ │ 淘淘商城_day20_课堂笔记.docx │ │ │ └─视频 │ 07-使用Jedis连接集群操作.avi │ 00-今日大纲.avi │ 01-RDB持久化方式.avi │ 02...

    javaee笔试题-ES2016_14353173:ES2016_14353173

    java ee笔试题 README 14353173 廖瑶雅 一、DOL 框架描述 DOL(The distributed operation layer)是一个用于编写并行应用程序的软件开发框架。DOL允许指定基于计算Kahn进程网络模型的应用程序和具有一个基于SystemC的...

Global site tag (gtag.js) - Google Analytics