JVM 将 synchronized 锁分为 无锁、偏向锁、轻量级锁、重量级锁状态。会根据情况, 依次进行升级
🔎无锁
线程之间不会引发安全问题,就不用加锁
🔎偏向锁
偏向锁不是真的 “加锁”, 只是先让线程针对锁做个标记(这个过程很快)
如果代码执行过程中没有其他线程来竞争这个锁, 那么就不用加锁
如果有其他线程竞争这个锁, 那么偏向锁就会真的加锁, 变成轻量级锁
偏向锁是synchronized内部做的工作
synchronized 会针对某个对象进行加锁, 偏向锁就是先去对加锁的对象做个标记
当有其他线程竞争这个锁时, JVM就会通知 偏向锁 升级成 轻量级锁
🔎轻量级锁
轻量级锁基于自旋锁实现
自旋锁
优点: 能够第一时间拿到锁
缺点: 一直消耗CPU,做不了其他事情(忙等)
如果锁竞争过于激烈(CPU消耗过大)
那么 synchronized 就会从 轻量级锁 升级成 重量级锁
🔎重量级锁
重量级锁基于挂起等待锁实现
挂起等待锁
优点: 不再消耗大量CPU资源
缺点: 锁被释放后不能第一时间拿到锁
当 synchronized 升级成重量级锁, 就意味着线程要暂时放弃 CPU, 由内核进行后续调度
🔎其他的锁优化
🌸锁消除
锁消除是编译器做的优化手段
非必要不加锁,
编译器发现代码不会引起线程安全问题却加锁的时候, 就会把锁给取消掉
举个栗子🥝
StringBuffer 是内部方法里面加了锁🔒
但是上述代码并不会引起线程安全问题
所以编译器就会进行优化(锁消除)
🌸锁粗化
锁粒度
synchronized 代码块中包含代码的多少
包含的代码越多, 锁粒度就越粗
包含的代码越少, 锁粒度就越细
一般写代码时, 希望锁的粒度细(代码块中包含的代码少)
这样串行的时间就会少一些, 并发的时间就会多一些
如果某个场景中涉及到频繁的加锁, 解锁操作
编译器就会将其优化, 优化成一个更粗粒度的锁
这就是锁粗化
该场景中由于涉及到频繁的加锁, 解锁操作
于是编译器就将其优化成了一个更粗粒度的锁
一个更粗粒度的锁
举个栗子🥝
滑稽老哥去给领导汇报工作
滑稽老哥给领导打电话汇报A工作情况
汇报完毕,滑稽老哥挂断电话
滑稽老哥给领导打电话汇报B工作情况
汇报完毕,滑稽老哥挂断电话
滑稽老哥给领导打电话汇报C工作情况
汇报完毕,滑稽老哥挂断电话
滑稽老哥打电话汇报工作时, 此时如果有其他的电话打给领导, 只能阻塞等待
汇报完毕, 再次汇报时, 可能其他人也在给领导打电话, 滑稽老哥阻塞等待
滑稽老哥需要再次拨打电话汇报其他工作情况(重新竞争锁)
针对上述情况, 编译器将该操作优化成了一个更粗粒度的锁
滑稽老哥向领导依次汇报A, B, C工作情况, 而不是分别打电话进行汇报
(滑稽老哥打电话给领导, 汇报工作A, B, C的情况, 挂断电话)
🔎结尾
创作不易,如果对您有帮助,希望您能点个免费的赞👍
大家有什么不太理解的,可以私信或者评论区留言,一起加油