Java是并发编程语言
- 前提:Java代码在编译(javac)后会变成Java字节码,字节码(.calss)被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令。
- Java编程语言允许线程访问共享变量:为了确保共享变量能被准确一直的更新,线程应该确保通过排它锁单独获得这个变量。
volatile用法及实现原理
用法
:一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的(可见性)实现原理
:有volatile变量修饰的共享变量进行读写操作时,JVM就会向处理器发送一条Lock前缀指令
Lock前缀的指令在多核CPU处理器下会引发以下两件事情
- 将当前处理器缓存行的数据写回到系统内存。缓存一致性。
- 这个写回到内存的操作会使其他CPU里缓存了该内存地址的数据无效。强制缓存行填充。
synchronized的实现原理与应用
Java中的每一个对象都可以作为锁。
- 对于普通同步方法,锁是当前实例对象
- 对于静态同步方法,锁是当前类的Class对象
- 对于同步方法块,锁是synchronized括号里配置的对象。
应用:当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
实现原理:
从JVM规范中可以看到Synchonized在JVM里的实现原理,JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。
代码块同步是使用monitorenter 和monitorexit指令实现的。
方法同步是使用另外一种方式实现的,细节在JVM规范里并没有 详细说明。但是,方法的同步同样可以使用这两个指令来实现。
monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有 一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter 指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。
synchronized锁结论:
同步代码块是依靠两个指令实现的(monitorenter和monitorexit)。
每一个对象都有一个monitor对象关联,一个monitor被持有后,对象会被锁定。
线程执行到monitorenter指令后会去获取monitor所有权,即获取锁。
Java对象头
synchronized用的锁是存在Java对象头里的。
Java对象头长度
长度 | 内容 | 说明 |
---|---|---|
32/64bit | Mark Word | 存储对象的hashCode和锁信息 |
32/64bit | Class Metadata Address | 存储到对象类型数据的指针 |
32/64bit | Array length | 数组的长度(如果当前对象是数组) |
Mark Word
默认存储对象的HashCode、分代年龄和锁标记位。
32位JVM的Mark Word的默认存储结构表
锁状态 | 25bit | 4bit | 1bit是否是偏向锁 | 2bit锁标志位 |
---|---|---|---|---|
无锁状态 | 对象的hashCode | 对象的分代年龄 | 0 | 01 |
运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。
Mark Word的状态变化表
在64位虚拟机下,Mark Word是64bit大小的,其存储结构表