ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

【并发编程】synchronized的偏向锁、轻量级锁、重量级锁详解

2022-01-30 15:03:11  阅读:217  来源: 互联网

标签:obj synchronized Thread parseInstance 详解 轻量级 debug 偏向 toPrintable


内存布局对应对应的锁状态

内存布局对应对应的锁状态.png

先说锁状态的变化结论

锁状态的变化流程图.png

偏向锁

  • 偏向锁是一种针对加锁操作的优化手段。
  • 在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了消除数据在无竞争情况下锁重入(CAS操作)的开销而引入偏向锁。
  • 对于没有锁竞争的场合,偏向锁有很好的优化效果。
  • JVM启用了偏向锁模式:jdk6之后默认开启
  • 新创建对象的Mark Word中的Thread Id为0,说明此时处于可偏向但未偏向任何线程,也叫做匿名偏向状态(anonymously biased)。

偏向锁延迟偏向

  • HotSpot 虚拟机在启动后开启偏向锁模式默认在4s后。
  • 为了减少初始化时间,JVM默认延时加载偏向锁。
//关闭延迟开启偏向锁
-XX:BiasedLockingStartupDelay=0
//禁止偏向锁
-XX:-UseBiasedLocking 

延迟开启偏向锁.png

  • 上图的代码可以验证:从无锁变为偏向锁(4秒)

偏向锁在无竞争的时候一直是偏向锁

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());
	}

  • 执行结果

偏向锁在无竞争.png

  • 从结果可以看出:无锁状态经过4秒变为偏向锁,之后的的状态一直是偏向锁!
  • 在进入同步代码块后,锁的偏向线程由0变为具体的线程。

在同步代码块外调用hashCode()方法

同步代码块外调用hashCode()结果.png

  • 进入同步代码块后锁升级为轻量级锁
  • 当对象可偏向(线程ID为0)时,MarkWord将变成未锁定状态,并只能升级成轻量锁。

在同步代码块内调用hashCode()方法

同步代码块内调用hashCode()结果.png

  • 直接升级为重量级锁
  • 当对象正处于偏向锁时,调用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());
	}
}


竞争不激烈的场景的运行结果

轻量级锁结果.png

重量级锁

  • 轻量级锁经过一次自选如果没有获取到锁,直接膨胀为重量级锁。
  • 重量级锁是基于 Monitor 机制,并且在 Monitor 中记录 hashCode

模拟竞争激烈的场景

  • 去掉不激烈的场景中的以下代码就是竞争激烈的场景
// 控制线程竞争时机
Thread.sleep(1);

竞争激烈的场景的运行结果

重量级锁结果.png

结束语

  • 获取更多有价值的文章,让我们一起成为架构师!
  • 关注公众号,可以让你对MySQL有非常深入的了解
  • 关注公众号,每天持续高效的了解并发编程!
  • 关注公众号,后续持续高效的了解spring源码!
  • 这个公众号,无广告!!!每日更新!!!
    作者公众号.jpg

标签:obj,synchronized,Thread,parseInstance,详解,轻量级,debug,偏向,toPrintable
来源: https://www.cnblogs.com/zfcq/p/15856884.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有