Synchronized锁升级过程

Synchronized锁升级过程

首先,每个对象都有一把锁。

对象的结构有三个部分:对象头,实例数据,对其填充字节(保证对象大小为8字节的倍数)

对象头由两部分组成:1.classpointer(指向当前对象在方法区中的类型数据)

2.Markword存储和当前对象运行时状态有关的数据

锁的信息就存在Markword中,在32JVMMarkword大小为32bit

Markword最后两位是锁标志位。

 

 

无锁:

1.     无竞争情况:无需任何保护,直接给各个线程调用。

2.     存在竞争:用不上锁的方式同步线程,CAS

 

偏向锁:

大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,为了降低获取锁的代价,才引入的偏向锁。

加锁:当线程1访问同步块并获取锁时, 会在锁对象的对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程进入和退出同步块时不需要进行CAS操作来加锁和解锁, 只需要比较当前线程的threadIDJava对象头中的threadID是否一致

升级:如果不一致(线程2竞争锁对象),先查看对象头中记录的线程1是否存活。如果不存活,锁对象重置为无锁状态,其他线程(线程2)可以竞争将其设为偏向锁;如果存活,查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

 

轻量级锁:

加锁:线程获取轻量级锁时会先把锁对象头MarkWord复制到线程的栈帧中创建的用于存储锁记录的空间,然后尝试使用CAS将对象头中的MarkWord替换为锁记录的地址。如果成功, 当前线程获得锁; 如果失败, 表示其它线程竞争锁, 当前线程便尝试使用自旋来获取锁, 之后再来的线程, 发现是轻量级锁, 就开始进行自旋.

升级:但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转

 

自适应自旋:自旋时间不再固定。当前正在自旋等待的线程,刚刚已经成功获得过锁,但是锁目前被其他线程占用,那么虚拟机就认为这次自旋也很有可能成功,进而允许更长时间的自旋。

 

Synchronized底层原理:

Synchronized被编译后会生成monitorenter monitorexit 指令。

在执行monitorenter时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1

在执行 monitorexit 指令后,将锁计数器设为 0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。

Monitor是依赖于操作系统的mutex lock实现的,每当挂起或者唤醒1个线程都要切换操作系统内核态。这个操作是比较重量级的,在一些情况下甚至切换时间本身会超出线程执行任务的时间。

 

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。

 

部分参考自:https://blog.csdn.net/tongdanping/article/details/79647337

 

posted @ 2021-04-27 19:28  星予  阅读(2005)  评论(0编辑  收藏  举报