synchronize的实现原理

参考:

https://blog.csdn.net/u012715840/article/details/58247556

https://blog.csdn.net/East_MrChiu/article/details/89163865

 

synchronize的实现原理_第1张图片

总结 : 1. 对象锁标记初始值为匿名偏向锁

2. 线程第一次获取锁是升级为偏向锁

3. 不止一个线程获取偏向锁,CAS 线程Id 失败的时候,升级为轻量级锁

4. 并发线程数太多,轻量级锁在CAS Mark Word 值失败时 升级为重量级锁,线程阻塞

问题 答案
synchronize 锁

同步代码块是使用monitorenter和monitorexit指令实现的

synchronize用的锁存储在Java对象头里

无状态锁,偏向锁,轻量级锁和重量级锁状态

32位 Mard Word

synchronize的实现原理_第2张图片

25 bit 对象hashCode 4bit 分代年龄 1bit 是否偏向锁 2bit 锁标志位

自旋锁

自适应自旋锁

自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁

优点: 不会引起调用者睡眠 缺点:一直占用CPU(可通过自旋次数及挂起线程来解决)

自适应自旋锁:自旋的时间限度不是一个固定值,由上一次同一个锁的自旋时间及锁的拥有者状态来决定

偏向锁

JVM启用了偏向锁模式(JDK6以上默认开启),新创建对象的Mark Word是未锁定,未偏向但可偏向状态,此时Mark Word中的Thread id为0,表示未偏向任何线程,也叫做匿名偏向(anonymously biased)

获取锁

1.第一个线程1尝试进入同步块时,Mark Word中线程ID为0,CAS将自己的线程ID设置到Mark Word中(偏向锁仅会执行一次CAS),在当前线程栈中由高到低顺序找到可用的Lock Record,将线程ID记录下

2.线程1再次进入同步块时,往当前线程的栈中添加一条Displaced Mark Word为空的Lock Record中,用来统计重入的次数,执行同步代码块

3.线程2进入同步块时,发现Mark Word中线程ID与自己不相符,这个时候就会引发偏向锁的撤销,线程2 CAS 操作竞争锁。

如果成功,则改写Mark Word 线程ID;继续是偏向锁

如果失败,当到达全局安全点时,线程1挂起,锁升级为轻量级锁,线程1唤醒继续执行

 

释放锁(竞争锁的时候才会释放)

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行)它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

偏向锁撤销

偏向锁释放

参考:

https://baijiahao.baidu.com/s?id=1630535202760061296&wfr=spider&for=pc

获取锁

1.对象锁状态为无锁状态,Mark Word信息拷贝存储到当前线程栈桢中Lock Record(又名锁记录,创建并拷贝)里

2. CAS 操作设置Mark Word指针信息(指向自己栈帧),和 Lock Record 中指针信息

成功:获得锁对象,更新轻量级锁

失败:如果是指针指向自己则直接进入代码块,否则轻量级锁就要膨胀为重量级锁

释放锁

1.CAS操作尝试把线程中复制的Lock Record对象替换当前的Mark Word

成功: 同步完成

失败:释放锁的同时,唤醒被挂起的线程(因为已升级为重量级锁)

重量级锁

获取锁

自旋次数超过JVM预期上限,竞争的线程就会把锁对象的Mark Word指向重锁,然后所有的竞争线程放弃自旋,逐个插入到monitor对象里的一个队列尾部,进入阻塞状态

 

synchronize的实现原理_第3张图片

synchronize的实现原理_第4张图片

 

 

你可能感兴趣的:(java并发编程)