三种表现形式:
对于普通同步方法,锁是当前实例方法
对于静态同步方法,锁是当前类的Class对象
对于同步方法块,锁是synchronized括号里配置的对象
从jvm是由jvm层面实现的,基于monitor对象实现方法同步和代码块同步。代码块同步是基于monitorenter和monitorexit指令实现的
java的对象头
synchronized用的锁是存在java对象头里。如果对象是数组类型,则虚拟机用3个字宽(word)存储对象头,非数组则用2字宽存储对象头。
对象头
| 长度 | 内容 | 说明 |
|---|---|---|
| 32/64bit | mark word | 存储hashcode,分代信息,锁标志 |
| 32/64bit | Class matedata address | 存储对象元信息的指针 |
| 32/64bit | array length | 数组长度(如果当前对象是数组的话) |
mark word 无锁状态存储结构
| 锁状态 | 25bit | 4bit | 1bit是否为偏向锁 | 2bit锁标识位 |
|---|---|---|---|---|
| 无锁状态 | 对象头的hashcode | 对象分代年龄 | 0 | 01 |
mark word 的状态变化
| 状态 | 25bit | 4bit | 1bit | 2bit | |
|---|---|---|---|---|---|
| 23bit | 2bit | 是否偏向 锁 | 锁标识位 | ||
| 轻量级锁 | 指向栈中锁记录的指针 | 00 | |||
| 重量级锁 | 指向互斥(重量级锁)的指针 | 10 | |||
| GC标记 | 空 | 11 | |||
| 偏向锁 | 线程ID | epoch | 对象分代年龄 | 1 | 01 |
64位虚拟机Mark word存储结构
锁状态 |
25bit | 31bit | 1bit | 4bit | 1bit | 2bit |
|---|---|---|---|---|---|---|
| cms_freee | 分代年龄 | 偏向锁 | 锁标识位 | |||
| 无锁 | unused | hashcode | 0 | 01 | ||
| 偏向锁 | 54bit threadId 2bit Epoch | 1 | 01 |
锁的升级和对比
锁等级依次是:无锁->偏向锁->轻量级锁->重量级锁。
锁可以升级但是不能降级
偏向锁
当一个线程访问同步代码块并获取锁时,会在对象头和栈帧中的锁记录里存储锁的偏向的线程ID,以后该线程再次进入和退出不需要进行CAS操作来解锁和加锁。只需简单地测试一下对象头的mark word里是否存储指向当前线程的偏向锁。比对成功者获取该锁,失败检查偏向锁标识,为0(未设置)CAS竞争锁。设置,尝试CAS将对象头的偏向锁指向当前线程。
偏向锁的撤销
竞争出现才会释放锁,即其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。
释放锁需要等待全局安全点(这个时间点上没有正在执行的字节码)
偏向锁的延迟开启和关闭
偏向锁默认是开启的,但是它会在应用程序启动几秒钟之后才激活。
可以使用jvm参数来关闭延迟:-XX:BiasedLockingStartupDelay=0
通过java参数来关闭偏向锁:-XX;+UseBiasedLocking
轻量级锁
轻量级锁加锁
线程在执行同步块之前,jvm会先将当前线程的栈帧中传教用于存储锁记录的空间,并将对象头的Mark word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word 替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程尝试使用自旋来获取锁。1.6前默认自旋10次。1.6以后为自适应次数。
轻量级锁解锁
轻量级锁解锁时,会使用cas操作将displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败表示当前锁存在竞争,锁就会膨胀成重量级锁。
