本文主要是介绍【并发编程】synchronized的偏向锁、轻量级锁、重量级锁详解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
内存布局对应对应的锁状态
先说锁状态的变化结论
偏向锁
- 偏向锁是一种针对加锁操作的优化手段。
- 在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了消除数据在无竞争情况下锁重入(CAS操作)的开销而引入偏向锁。
- 对于没有锁竞争的场合,偏向锁有很好的优化效果。
- JVM启用了偏向锁模式:jdk6之后默认开启
- 新创建对象的Mark Word中的Thread Id为0,说明此时处于可偏向但未偏向任何线程,也叫做匿名偏向状态(anonymously biased)。
偏向锁延迟偏向
- HotSpot 虚拟机在启动后开启偏向锁模式默认在4s后。
- 为了减少初始化时间,JVM默认延时加载偏向锁。
//关闭延迟开启偏向锁
-XX:BiasedLockingStartupDelay=0
//禁止偏向锁
-XX:-UseBiasedLocking
偏向锁在无竞争的时候一直是偏向锁
public static void main(String[] args) throws InterruptedException {
log.debug(Thread.currentThread().getName() + "最开始的状态。。。\n"
+ ClassLayout.parseInstance(new Object()).toPrintable());
// HotSpot 虚拟机在启动后有个 4s 的延迟才会对每个新建的对象开启偏向锁模式
Thread.sleep(4000);
Object obj = new Object();
new Thread(new Runnable() {
@Override
public void run() {
log.debug(
Thread.currentThread().getName() + "开始执行准备获取锁。。。\n" + ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
log.debug(Thread.currentThread().getName() + "获取锁执行中。。。\n"
+ ClassLayout.parseInstance(obj).toPrintable());
}
log.debug(Thread.currentThread().getName() + "释放锁。。。\n" + ClassLayout.parseInstance(obj).toPrintable());
}
}, "thread1").start();
Thread.sleep(5000);
log.debug(Thread.currentThread().getName() + "结束状态。。。\n" + ClassLayout.parseInstance(obj).toPrintable());
}
- 从结果可以看出:无锁状态经过4秒变为偏向锁,之后的的状态一直是偏向锁!
- 在进入同步代码块后,锁的偏向线程由0变为具体的线程。
在同步代码块外调用hashCode()方法
- 进入同步代码块后锁升级为轻量级锁
- 当对象可偏向(线程ID为0)时,MarkWord将变成未锁定状态,并只能升级成轻量锁。
在同步代码块内调用hashCode()方法
- 直接升级为重量级锁
- 当对象正处于偏向锁时,调用HashCode将使偏向锁强制升级成重量锁。
偏向锁撤销:自己验证wait和notify
- 调用锁对象的obj.hashCode()或System.identityHashCode(obj)方法会导致该对象的偏向锁被撤销。
- 因为对于一个对象,其HashCode只会生成一次并保存,偏向锁是没有地方保存hashcode的。
- 轻量级锁会在锁记录中记录 hashCode。
- 重量级锁会在 Monitor 中记录 hashCode。
- 当对象可偏向(线程ID为0)时,MarkWord将变成未锁定状态,并只能升级成轻量锁。
- 当对象正处于偏向锁时,调用HashCode将使偏向锁强制升级成重量锁。
- 偏向锁状态执行obj.notify() 会升级为轻量级锁。
- 调用obj.wait(timeout) 会升级为重量级锁。
轻量级锁
- 倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段,此时Mark Word 的结构也变为轻量级锁的结构。
- 轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间多个线程访问同一把锁的场合,就会导致轻量级锁膨胀为重量级锁。
- 轻量级锁在降级的时候直接变为无锁状态!(查看之前在同步代码块外调用hashCode()方法)
模拟竞争不激烈的场景
@Slf4j
public class TestMemory {
public static void main(String[] args) throws InterruptedException {
log.debug(Thread.currentThread().getName() + "最开始的状态。。。\n"
+ ClassLayout.parseInstance(new Object()).toPrintable());
// HotSpot 虚拟机在启动后有个 4s 的延迟才会对每个新建的对象开启偏向锁模式
Thread.sleep(4000);
Object obj = new Object();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
log.debug(Thread.currentThread().getName() + "开始执行thread1。。。\n"
+ ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
log.debug(Thread.currentThread().getName() + "获取锁执行中thread1。。。\n"
+ ClassLayout.parseInstance(obj).toPrintable());
}
log.debug(Thread.currentThread().getName() + "释放锁thread1。。。\n"
+ ClassLayout.parseInstance(obj).toPrintable());
}
}, "thread1");
thread1.start();
// 控制线程竞争时机
Thread.sleep(1);
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
log.debug(Thread.currentThread().getName() + "开始执行thread2。。。\n"
+ ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
log.debug(Thread.currentThread().getName() + "获取锁执行中thread2。。。\n"
+ ClassLayout.parseInstance(obj).toPrintable());
}
log.debug(Thread.currentThread().getName() + "释放锁thread2。。。\n"
+ ClassLayout.parseInstance(obj).toPrintable());
}
}, "thread2");
thread2.start();
Thread.sleep(5000);
log.debug(Thread.currentThread().getName() + "结束状态。。。\n" + ClassLayout.parseInstance(obj).toPrintable());
}
}
竞争不激烈的场景的运行结果
重量级锁
- 轻量级锁经过一次自选如果没有获取到锁,直接膨胀为重量级锁。
- 重量级锁是基于 Monitor 机制,并且在 Monitor 中记录 hashCode
模拟竞争激烈的场景
// 控制线程竞争时机
Thread.sleep(1);
竞争激烈的场景的运行结果
结束语
- 获取更多有价值的文章,让我们一起成为架构师!
- 关注公众号,可以让你对MySQL有非常深入的了解
- 关注公众号,每天持续高效的了解并发编程!
- 关注公众号,后续持续高效的了解spring源码!
- 这个公众号,无广告!!!每日更新!!!
这篇关于【并发编程】synchronized的偏向锁、轻量级锁、重量级锁详解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!