1.volatile
1.1.为什么要使用volatile
volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换。
1.2.volatile的定义
Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。
1.3.volatile的实现原理
volatile保证可见性的原理是,有volatile变量修饰的共享变量进行写操作的时候,除了将操作数写回到缓存,还会发送一条Lock前缀的指令,这条指令做了两件事情:
1)将当前处理器缓存行的数据写回到系统内存;
2)这个写会内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
2.synchronized
2.1.简介
synchronized是一种重量级锁,但是,随着Java SE 1.6引入了偏向锁和轻量级锁之后,有些情况下它就并不那么重了。
2.2.synchronized的实现原理
JVM基于进入和退出monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明。但是,方法的同步同样可以使用这两个指令来实现。
monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且仅当一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。
2.3.锁标记的存储
synchronized用的锁存在Java对象头中的Mark Word里。无锁状态下,Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。32位的JVM的Mark Word的默认存储结构如下表所示。
锁状态 | 25bit | 4bit | 1bit 是否是偏向锁 | 2bit 锁标志位 |
---|---|---|---|---|
无锁状态 | 对象的HashCode | 对象的分代年龄 | 0 | 01 |
在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。
锁状态 | 23bit | 2bit | 4bit | 1bit 是否是偏向锁 | 2bit 锁标志位 |
---|---|---|---|---|---|
偏向锁 | 线程ID | Epoch | 对象分代年龄 | 1 | 01 |
锁状态 | 30bit | 2bit 锁标志位 |
---|---|---|
轻量级锁 | 指向栈中锁记录的指针 | 00 |
锁状态 | 30bit | 2bit 锁标志位 |
---|---|---|
重量级锁 | 指向互斥量(重量级锁)的指针 | 10 |
锁状态 | 30bit | 2bit 锁标志位 |
---|---|---|
GC标记 | 空 | 11 |