1. 锁分类
无锁->偏向锁->轻量级锁->重量级锁
synchronized属于重量级锁,monitor是基于底层os的mutex Lock实现了,挂起线程和恢复线程都需要内核态完成,都需要切换CPU状态来完成。
Monitor与对象以及线程如何关联?
1.对象被某个线程锁住,则该对象
2. synchronized锁升级流程
synchronized用的锁依赖释放偏向锁标志位和锁标志位。
- 偏向锁:MarkWord存储的是偏向的线程ID。
- 轻量锁:MarkWord存储指向线程栈中Lock Recoard的指针。
- 重量锁:MarkWord存储的是指向堆中的monitor对象的指针。
3. 偏向锁
单线程竞争:当一段同步代码一直被同一个线程多次访问,后续访问自动获取锁。
偏向锁默认有4s的延迟。
竞争出现才释放偏向锁。
Java 15取消了偏向锁。
4. 轻量级锁
当有另外线程逐步来竞争锁的时候,偏向锁会升级为轻量级锁。
竞争线程尝试CAS更新对象头失败,会等待到全局安全点 (此时不会执行任何代码) 撤销偏向锁。
轻量级锁的加锁
JVM会为每个线程在当前线程的栈帧中创建用于存储记录的空间,即Displaced Mark Word。若一个线程获得锁时,发现是轻量级锁,会把锁的MarkWord复制到Displaced Mark Word里面。然后尝试用CAS将锁的Mark Word替换为指向锁记录的指针。
成功,当前线程获得锁。失败,表示Mark Word已经被替换成了其他线程的锁记录,说明在与其他线程竞争锁,当前线程就尝试使用自旋来获取锁。
轻量级锁的释放
在释放锁时,当前线程会使用CAS操作将Displaced Mark Word的内容复制回锁的Mark Word里面。
没有竞争,那么这个复制的操作会成功。如果其他线程因为自旋多次导致轻量级锁升级为重量级锁,那么CAS操作会失败,此时会释放锁并唤醒被阻塞的线程。
5. 重量级锁
有大量线程参与锁的竞争,冲突性很高。
synchronized的重量级锁基于进入和退出Monitor对象实现的。在编译时会将同步块的开始位置插入monitor enter指令,在结束位置插入monitor exit指令。
当线程执行到monitor enter指令时,会尝试获取对象所对应的Monitor所有权,如果获取到了锁,会在Monitor的owner中存放当前线程的id,这样它将处于锁定状态,除非退出同步块,否则其他线程无法获取到这个Monitor。
指向互斥量的指针。
6. 锁和哈希码
不推荐修改hashCode。
当一个对象计算过hashCode后,再也无法进入偏向锁状态。
偏向锁过程中遇到一致哈希计算请求,立马撤销偏向模式,膨胀为重量级锁。
- 无锁状态:对象的hashCode()方法第一次被调用时,JVM会生成对应的identity hash code值并将该值存储到Mark Word中。
- 偏向锁:线程获取偏向锁,或用线程id和epoch值覆盖identity hash code所在的位置。如果一个对象的hashCode() 方法已被调用过一次之后,这个对象不能被设置偏向锁。为了保证统一对象前后两次调用hashCode() 方法得到的结果一致。
- 轻量锁:JVM会在当前线程的栈帧中创建一个锁记录(Lock Record)空间,用于存储锁对象的Mark Word拷贝。所以轻量锁可以和identity hash code共存,哈希码和GC年龄保存于此,释放锁后,这些信息写回对象头中,
- 重量级锁:Mark Word的重量级锁指针指向重量级锁的ObjectMonitor类,里面有字段记录非加锁状态下的Mark Word,释放后也会写回。
7. JIT编译器对锁的优化
Just in time compiler即时编译器。
7.1 锁消除
锁消除。JIT编译器会无视它,每次new o,synchronized(o) 就会无视它。
class Test05 {
Object object = new Object();
public void print() {
Object o = new Object();
synchronized (o) {
System.out.println("Lock:" + o.hashCode() + " Object:" + object.hashCode());
}
}
}
public class demo05 {
public static void main(String[] args) {
Test05 test05 = new Test05();
for(int i=0; i<10; i++) {
test05.print();
}
}
}
7.2 锁粗化
一次申请锁。
public static void main(String[] args) {
Object o = new Object();
for(int i=0; i<10; i++) {
synchronized (o) {
System.out.println("1");
}
synchronized (o) {
System.out.println("1");
}
}
}