MarkWord
包含对象的hashCode、分代年龄、轻量级锁指针、重量级锁指针、GC标记、偏向锁线程ID和偏向锁时间戳。
无锁、偏向锁、轻量级锁、重量级锁
1. 无锁状态:线程对共享数据并没有发生竞争,因此也就不需要加锁。
2. 偏向锁状态:当只有一个线程访问共享数据时,为了减少不必要的锁竞争,JVM 会将对象头 Mark Word 的标记设置为偏向锁。这个线程第一次访问对象时,JVM 会记录下该线程 ID,并在之后的访问中判断该线程 ID 是否与记录的一致。如果一致,则该线程无需竞争锁,可以直接访问共享数据,否则需要撤销偏向锁并竞争锁。
3. 轻量级锁状态:当多个线程同时访问同一个对象时,偏向锁就会失效,对象头 Mark Word 的标记变为轻量级锁。首先,当前线程会尝试使用 CAS(compare-and-set)操作来获取锁。如果 CAS 成功,则当前线程顺利进入同步块执行,否则会使用自旋来等待锁的释放。自旋过程中,如果锁被其他线程持有时间太长或者自旋次数达到某个阈值,线程就会放弃自旋,将锁升级为重量级锁。
4. 重量级锁状态:如果自旋次数达到某个阈值或锁被其他线程持有时间太长,JVM 会将锁升级为重量级锁。重量级锁需要操作系统级别的互斥量(mutex)来实现线程同步,因此会涉及到用户态和内核态之间的切换,性能开销比较大。
锁升级过程
偏向锁升级为轻量级锁
当第一个拿到偏向锁的线程执行时,遇到有新的线程在询问统一代码块的锁时就有可能会升级成轻量级锁,为什么说是有可能呢?因为偏向锁不会自动释放,此时第2个线程询问锁时会出现2种情况
1.第一个线程已经执行完毕,那么CAS操作将Mark Word设置为Null,第二个线程获取偏向锁,此时不会升级成轻量级锁
2.第一个线程未执行完毕,此时第二个线程获取锁失败,那么会进行自旋,当自旋达到一定次数后,就会升级成轻量级锁
当一个线程想要获取某个对象的锁时,假如看到锁标志位为00, 那么就知道是轻量级锁;这时,线程会在自己的线程虚拟机栈中开辟一块 Lock Record 空间; Lock Record存放的是 Mark Word的副本、owner对象的指针(指向该对象);对象头前30bit记录了指向 Lock Record 的指针;完成了线程和对象的绑定,线程抢到了该对象的锁
补充
-
synchronized关键字不能继承。
对于父类中的 synchronized 修饰方法,子类在覆盖该方法时,默认情况下不是同步的,必须显示的使用 synchronized 关键字修饰才行。
-
在定义接口方法时不能使用synchronized关键字。
-
构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。