synchronized原理,偏向锁、轻量级锁、重量级锁

锁信息存放:

java对象:对象头、实例数据、对齐填充
对齐填充不是必要的、由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍、所以他仅仅起着占位符的作用

对象头:Mark Word、指向类的指针、数组长度(只有数组对象才有)
	指向类指针:是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,长度:32位JVM--32bit,64位JVM--64bit。
	Mark Word:用于存储对象自身的允许时数据,如哈希码(HashCode)、GC分代年龄、锁标志位、
		 	 线程持有的锁、偏向线程 ID、偏向时间戳等等,它是实现轻量级锁和偏向锁的关键。
			长度:32位JVM--32bit,64位JVM--64bit。
	数组长度(只有数组对象才有):32位JVM、64位JVM--32bit。

锁的状态中Mark Word记录:
Mark Word

无锁状态:Mark Word记录: 对象的哈希码(HashCode)、GC分代年龄、是否偏向锁、锁标志位

偏向锁状态:Mark Word记录: 线程ID、Epoch(偏向锁的时间戳)、GC分代年龄、是否偏向锁、锁标志位

设计理念:实际发现,在每次的多线程中总是同一个线程A访问,为了提高性能,节省资源消耗,就产生了偏向锁,锁总是被第一个占用它的线程拥有,
	      每次线程进入同步代码块之前就会检测Mark Word的线程ID是否为线程A,如果是就会直接执行同步代码块。


偏向锁获取过程:
	1.首先获取锁对象的Mark word判断是否为可偏向状态(Mark Work中偏向锁的标记是否为1)
	2.如果是可偏向状态,则通过 CAS 操作,把当前线程的 ID 写入到 Mark Word
		 a.如果CAS成功,就会把Mark Word中锁标志改为‘01’,表示已经获得了锁对象的偏向锁,接着执行同步代码块。
		 b.如果CAS失败,说明有其他线程已经获得了偏向锁,这种情况说明当前锁存在竞争,需要撤销已获得偏向锁的线程,
		并且把它持有的偏向锁升级/膨胀为轻量级锁。
	3.如果是已偏向状态,需要检查 Mark Word 中存储的线程ID是否等于当前线程的线程ID
		a) 如果相等,不需要再次获得锁,可直接执行同步代码块
		b) 如果不相等,说明当前锁偏向于其他线程,需要撤销偏向锁并升级/膨胀到轻量级锁
	
偏向锁的撤销过程:偏向锁不会主动释放(撤销),只有遇到其他线程竞争时才会执行撤销,由于撤销需要知道当前持有该偏向锁的线程栈状态,
			因此要等到safepoint时执行,此时持有该偏向锁的线程(A)有两种情况;
safepoint:可以理解成是在代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,说明虚拟机当前的状态是安全的,如果有需要,可以在这个位置暂停。

		a.撤销----A线程已经退出同步代码块,或者已经不再存活,则直接撤销偏向锁,变成无锁状态

		b.升级----A线程还在同步代码块中,则将A线程的偏向锁升级为轻量级锁,当前线程执行轻量级锁状态下的锁获取步骤

轻量级锁状态:Mark Word记录:指向栈中锁记录的指针、锁标志位

设计理念:在某个业务功能中,有可能会被并发访问,也有可能不会被并发访问,这个业务块就做成同步块。实际发现,
	      对这个同步块访问不是每次都是并发访问,有可能没有出现并发访问的情况,而且这种情况还很多,
	      为了提高性能,节省资源消耗,就设计成轻量级锁。

轻量级锁获取过程:
	1.在进入同步代码块的时候,JVM会检测当前线程对象的锁状态
	如果同步对象锁状态为无锁状态,JVM会在当前线程的栈中创建一个锁记录(Lock Record)的空间,
	用于存储锁对象的Mark Word的拷贝(Displaced Mark Word),如果是处于偏向锁状态,则使用原来的锁记录。
	2.拷贝之后,当前线程使用CAS操作,把当前线程对象Mark Word的锁标志为“00”
		a.CAS操作成功,则将锁对象的Mark Word更新为指向栈中锁记录的指针,并将锁记录里的owner指针指向锁对象的mark word。
		b.CAS操作失败,jvm先检查对象MarkWord是否指向当前线程栈帧中的锁记录(Lock Record)。
			i.如果是,表示锁重入;然后当前线程栈帧中增加一个锁记录第一部分(Displaced Mark Word)为null,
			并指向Mark Word的锁对象,起到一个重入计数器的作用。
			ii.如果否,表示有竞争,锁被别的线程获取到了,这个时候当前线程使用自旋(默认10次)来等待获取锁,
			等待次数达到阈值仍未获取到锁,则升级为重量级锁
	自旋:让一个线程去执行一个无意义的循环,用于消耗cpu,目的为了等待自旋结束后,重新去竞争锁,
		避免在很短时间之内,对线程进行阻塞、唤醒操作,从而节省资源,提高程序运行的性能。

轻量级锁解锁过程:
	1.通过CAS操作把尝试把线程栈帧中复制的锁记录中的(Displaced Mark Word)替换当前对象头的MarkWord(即还原对象头)
		a.替换成功,则释放锁
		b.替换失败,说明已经膨胀为重量级锁,则在执行完同步块释放锁同时唤醒被挂起的线程

重量级锁状态:Mark Word记录:指向互斥锁(重量级锁)的指针、锁标志位
重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被称为互斥锁。
每一个JAVA对象都会与一个监视器monitor关联,我们可以把它理解成为一把锁,当一个线程想要执行同步代码块时,该线程得先获取到锁对象对应的monitor。
当一个monitor被持有后,它将处于锁定状态。

重量级锁加锁过程:
	1.调用omAlloc分配一个ObjectMonitor对象,把Mark Word锁标志置为‘10’,然后Mark Word存储指向ObjectMonitor对象的指针。
	ObjectMonitor对象有两个队列和一个指针,每个需要获取锁的线程都包装成ObjectWaiter对象

	2.多个线程同时执行同一段同步代码时,ObjectWaiter先进入_EntryList队列,当某个线程获取到对象的monitor以后进入_Owner区域,
	并把monitor中的owner变量设置为当前线程同时monitor中的计数器count+1;

重量级锁释放过程:

	1.若同步块中的线程调用wait()方法,则释放持有的monitor,owner遍历值为null,count-1,同时线程进入_WaitSet等待被唤醒

	2.若当前同步块执行完毕,则也释放持有的monitor,owner遍历置为null,count-1

锁的优缺点:
锁优缺点

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

忆亦何为

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值