synchronized 在多线程并发中一直是元老级角色,在java SE 1.6 中为了减少获取锁和释放锁引入了偏向锁、轻量级锁、以及重量级锁。
synchronized 实现同步基础
java 中的每一个对象可以作为锁,具体的是以下三种形式:
- 对于普通的同步方法 , 锁的是当前实例对象。
- 对于静态同步方法,锁的是当前类的Class 对象。
- 对于同步方法块,锁的是Synchonized括号里面的代码块。
Synchonized 在JVM 的实现原理
JVM 进入和退出Monitor 对象实现方法同步和代码同步,但是两种的实现细节不一样。代码块同步是使用monitorenter和monitorexit 来实现,而方法同步是使用的另外一种形式来实现的。
monitorenter 指令是在编译后插入到同步代码块开始的位置,而monitorexit是插入到方法结束和异常处,JVM 要保证美国monitorenter 必须有对应的monitorexit与之匹配。任何对象都有一个monitor 关联,当且一个monitor 被持有后,它将处于锁定状态。线程执行到monitorenter 时,将会尝试取对象所对应的monitor的所有权。
Java 对象头
synchonized 用的锁时存在Java 对象头里面的。如果对象时数组类型,则虚拟机用3给字宽存储对象头,如果对象是非数组类型,则用2个字宽存储对象头。 (在32 位虚拟机里。1字宽等于4字节 ,等于32bit)
长度 | 内容 | 说明 |
---|---|---|
32/64bit | Mark Word | 存储hashCode 或者锁新形象 |
32/64bit | Class Metadata Address | 存储到对象类型数据的指针 |
32/32bit | Array length | 数组长度 |
java 对象头里面的Mark Word 里默认存储的对象是HashCode 、分代年龄和锁标记位。
25bit | 4bit | 1bit | 2bit |
---|---|---|---|
对象的Hashcode | 对象分代年龄对象 | 是否是偏向锁 | 锁的标志位 |
不同的锁表示的不一样,上面这个是无锁的状态
锁状态 | 1-30bit | 31-32bit |
---|---|---|
轻量级锁 | 指向栈中记录的指针 | 锁标记(00) |
锁状态 | 1-30bit | 31-32bit |
---|---|---|
重量级锁 | 指向互斥量(重量级锁)的指针 | 锁标记(10) |
锁状态 | 1-30bit | 31-32bit |
---|---|---|
GC标记 | 空 | 锁标记(11) |
锁状态 | 1-23bit | 24-25bit | 26-29bit | 30bit | 31-32bit |
---|---|---|---|---|---|
偏向锁 | 线程ID | Epoch | 对象分代年龄 | 是否是偏向锁(1) | 锁标记(01) |
在64位虚拟机下,Work Mord 就是64 的大小
锁状态 | 1-54bit | 55-56bit | 57bit | 58-61bit | 62bit | 63-64bit |
---|---|---|---|---|---|---|
偏向锁 | ThreadId | Epoch | cms_free | 对象分代年龄 | 是否是偏向锁(1) | 锁标记(01) |