目录
锁升级过程和原理
同步方法与一般方法字节码对比
public class SynchronizedTest1 {
public static void main(String[] args) {
synchronized (SynchronizedTest1.class){
int a = 1;
}
}
}
0 ldc #2 <SynchronizedTest/SynchronizedTest1>
2 dup
3 astore_1
4 monitorenter//监视器的进入(获取监视器)
5 iconst_1
6 istore_2
7 aload_1
8 monitorexit
9 goto 17 (+8)
12 astore_3
13 aload_1
14 monitorexit//监视器的退出(释放监视器)
15 aload_3
16 athrow
17 return
public class SynchronizedTest1 {
public static void main(String[] args) {
//synchronized (SynchronizedTest1.class){
int a = 1;
//}
}
}
0 iconst_1
1 istore_1
2 return
线程再获取锁时 实际上就是获取一个监视器对象(monitor)
monitor可以认为是一个同步对象 所有的java对象天生携带monitor
获取monitor的过程即排他的过程
任意时刻只有一个线程能获取到由synchronized保护的对象的监视器
对象头中的锁信息
对象头包括两部分数据
MarkWord 标记字段
klassPointer 类型指针
数组类型还有一个int类型的数组长度
其中MarkWorld记录了对象和锁有关的信息
当这个对象被synchronized关键字当成同步锁使用时
围绕这个锁的一系列操作都于markWorld有关
MarkWorld在32位的JVM中的长度是32位
在64位的JVM中长度是64位
markworld在不同锁的状态下存储的内容不同
示意图如下
![在这里插入图片描述](https://img-blog.csdnimg.cn/b0f737ca03114ff0945a4eea7a6052dd.png#pic_center)
锁升级即jvm随着某对象被线程争抢的剧烈程度增加
对该对象锁的处理从偏向锁→轻量级锁→重量级锁的升级过程
锁升级中涉及的四种锁
锁升级的过程
-
当此对象没有被当成锁 markWord记录对象的HashCode
此时对象为无锁状态
锁标志位位01 是否偏向锁位为0
-
当对象被作为同步锁使用且有一个线程A获得了锁时
锁标志位还是01是否偏向锁位设为1
前32位记录获取锁的线程的id
- 当线程A再次试图获取锁时 JVM发现同步锁标志位位01偏向锁位为1
即此对象处于偏向锁状态
同时检查markword中记录的线程id是否为自己的id
如果是 可以执行同步锁的代码
-
当线程B试图获得锁时 此对象依然为偏向锁状态但其记录的线程id不是B线程的id
那么B会用CAS操作试图获得锁
-
如果获得成功
线程b将markword中的线程id改为自己的id并执行代码
-
如果抢锁失败
执行下一步骤
-
抢锁失败说明此锁有一定竞争
JVM将此锁升级为轻量锁
-
在前线程的线程栈中开辟单独空间 保存指向对象锁的MarkWord
称为锁记录LockRecord
-
同时在对象锁markWord中保存指向这篇空间的指针
-
上面两个cas操作都为cas操作 如果保存成功说明抢到了锁
将markWord中的标志位改为00
可以执行同步锁代码
-
如果保存失败 代表抢锁失败 执行下一步骤
-
轻量级锁抢锁失败 JVM会使用自旋锁 即不断重试抢锁
默认自旋次数为10次
如果10次后依然失败 进行下一步骤
-
升级为重量锁
锁的标志位改为10
此状态下 未抢到锁的线程会被阻塞排队
当后续线程尝试获取锁时
如果锁已被占用 且发现其为重量级锁
则直接将自己阻塞挂起
等待将来被唤醒