《Java并发编程的艺术》笔记二——Java并发机制的底层实现原理.md

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014453515/article/details/77146018

0.Java代码执行过程

Java代码在编译之后会变成Java字节码,Java字节码被类加载器加载到JVM中,JVM执行字节码,最终转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖与JVM的实现和CPU的执行。 b本节探讨下Java并发机制的实现原理。

1. volatile的应用

在并发编程中synchronized和volatile都扮演者重要角色。volatile是轻量级的synchronized,作用是在多线程中保证共享变量的“可见性”。可见性的意思是,当一个线程修改 一个共享变量时,另一个线程能读到这个修改的值。 如果volatile变量修饰符使用恰当的话,他会比synchronized的使用执行成本更低,因为它不会线程的上下文切换和调度。

volatile 的定义与实现原理

Java语言规范第3版中的volatile 定义如下: Java语言允许线程访问共享变量,为了保证共享变量能够被准确一致的更新,线程应该确保通过排它锁单独获得这个变量。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。

volatile是如何来保证可见性的呢?

volatile instance = new Singleton();

这行代码在X86处理器下编译生成的汇编指令中会多出一行汇编代码:lock addl $0x0,(%esp);

这行代码会在用volatile修饰共享变量时出现。Lock前缀的指令在多核处理器下会引发两件事:

1.将当前处理器缓存行的数据写回到系统内存。

2.这个写回内存的操作,会使其他CPU中缓存了该内存地址的数据无效。

为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存中后,再进行操作。但是,操作完之后不知道何时写回内存中。如果对声明了volatile的变量进行写操作,JVM就会向处理器发出一条带有LOCK前缀的指令,告诉CPU,将这个变量所在的缓存行的数据写回到系统内存。

所以总结下,volatile的两条实现原则:

1)Lock前缀执行会引起处理器将缓存写回到内存。

2)一个处理器的缓存写回到内存后,会导致其他处理器的缓存无效。

2.synchronized的实现原理与应用

本节介绍JavaSE1.6 中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结果和升级过程。

2.1synchronized实现同步的基础:Java中的每一个对象都可以为锁。

具体表现为以下3中形式:

  • 对于普通的同步方法,锁是当前实例对象

  • 对于静态同步方法,锁是当前类的Class对象

  • 对于同步方法,锁是Synchronized括号里配置的对象。

1.当一个线程访问同步代码块时,首先它必须得到锁,退出和抛出异常时必须释放锁。

2.JVM通过进入和退出Monitor对象来实现方法同步和代码块的同步,但是两者的实现细节不一样。代码块的同步是使用monitorenter和monitorexit指令实现的,方法同步JVM规范中没有详细说明。

3.monitorenter指令是在编译后插入同步代码块的起始位置,而Monitorexit是插入在方法结束处和异常处。

4.JVM要保证每个monitorenter都有一个monitorexit与之对应,。任何一个对象都有一个monitor与之关联,而且当一个monitor被持有后,他将处于锁定状态。当线程执行到monitorenter指令时,将会尝试获取对象对应的monitor的所有权,即尝试获得对象的锁。

2.2对象头

synchronized用的锁是存在Java对象头里的。如果对象是非数组类型,则用两个字宽存储对象头,如果对象是数组,则虚拟机用3个字宽(Word)存储对象头,其中一个字宽存储的是数组的长度。

对象头分为两个部分:Mark Word和类元数据地址

  • Mark Word默认存储对象自身的运行时数据,如哈希码,GC分代年龄,锁标记位
  • 类元数据地址是类型指针,即对象指向他的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例。如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的元数据信息并不一定要经过对象本身。

猜你喜欢

转载自blog.csdn.net/u014453515/article/details/77146018