三种表现形式:

对于普通同步方法,锁是当前实例方法
对于静态同步方法,锁是当前类的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替换回到对象头,如果成功,则表示没有竞争发生。如果失败表示当前锁存在竞争,锁就会膨胀成重量级锁。