synchronized锁升级过程及其实现原理

本文链接:https://blog.csdn.net/wangyy130/article/details/106495180

问:为什么会有锁升级的过程呢
答:在java6以前synchronized锁实现都是重量级锁的形式,效率低下,为了提升效率进行了优化,所以出现了锁升级的过程。
问:我们通常说synchronized锁是重量级锁,那么为什么叫他重量级锁?
答:因为synchronized执行效率太低。在java1.6以前每次调用synchronized加锁时都需要进行系统调用,系统调用会涉及到用户态和内核态的切换,系统调用会经过0x80中断,经过内核调用后再返回用户态。此过程比较复杂时间比较长所以通常叫synchronized为重量级锁
误区:其实锁升级过程中涉及到的锁偏向锁,轻量级锁都是synchronized锁的具体实现所要经历的过程,他们并不是单独的锁。只是给他们这几种锁的状态起了一个名字而已。

synchronized锁有四种状态,无锁,偏向锁,轻量级锁,重量级锁,这几个状态会随着竞争状态逐渐升级,锁可以升级但不能降级,但是偏向锁状态可以被重置为无锁状态

CAS

在介绍synchronized锁升级过程之前,我们需要先了解cas的原理,为什么呢?因为cas贯穿了整个synchronized锁升级的过程。

CAS : compare and swap 或者 compare and exchange 比较交换。
当我们需要对内存中的数据进行修改操作时,为了避免多线程并发修改的情况,我们在对他进行修改操作前,先读取他原来的值E,然后进行计算得出新的的值V,在修改前去比较当前内存中的值N是否和我之前读到的E相同,如果相同,认为其他线程没有修改过内存中的值,如果不同,说明被其他线程修改了,这时,要继续循环去获取最新的值E,再进行计算和比较,直到我们预期的值和当前内存中的值相等时,再对数据执行修改操作。

CAS具体流程如下下图:
在这里插入图片描述

他是为了实现java中的原子操作而出现的。为了保证在比较完成后赋值这两个操作的原子性,jvm内部实现cas操作时通过LOCK CMPXCHG指令锁cpu总线方式实现原子操作的。

对象头

synchronized用的锁是存在java对象头里的。

32位java对象头结构如下表所示:

在这里插入图片描述

对于64位的java对象头其余信息基本不变,只是中间有关于对象hashcode值和之后加锁信息的位数加大以外,其他基本不变。
64位虚拟机系统下java对象头在不同锁状态下的状态变化如下表所示:

在这里插入图片描述

如上图所示:其中最后两位代表是否加锁的标志位。锁标志位如果是01的话需要根据前一位的是否为偏向锁来判断当前的锁状态,如果前一位为0则代表无锁状态,如果为1则代表有偏向锁。
后两位:00代表轻量级锁,10代表重量级锁,11代表GC垃圾回收的标记信息。

偏向锁

偏向锁产生的原因?
大多数情况下,锁不紧不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。

获取偏向锁流程:
当一个线程访问同步块时,会先判断锁标志位是否为01,如果是01,则判断是否为偏向锁,如果是,会先判断当前锁对象头中是否存储了当前的线程id,如果存储了,则直接获得锁。如果对象头中指向不是当前线程id,则通过CAS尝试将自己的线程id存储进当前锁对象的对象头中来获取偏向锁。当cas尝试获取偏向锁成功后则继续执行同步代码块,否则等待安全点的到来撤销原来线程的偏向锁,撤销时需要暂停原持有偏向锁的线程,判断线程是否活动状态,如果已经退出同步代码块则唤醒新的线程开始获取偏向锁,否则开始锁竞争进行锁升级过程,升级为轻量级锁。

另一个博主的解释:

当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

偏向锁获取流程如下图:
在这里插入图片描述

在高并发下可以关闭偏向锁来提升性能,通过设置JVM参数 -XX:-UseBiasedLocking=false。

轻量级锁

当出现锁竞争时,会升级为轻量级锁。
在升级轻量级锁之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间即将对象头中用来标记锁信息相关的内容封装成一个java对象放入当前线程的栈帧中,这个对象称为LockRcord,然后线程尝试通过CAS将对象头中mark word替换为指向锁记录(lockrecord)的指针。如果成功则当前线程获取锁,如果失败则使用自旋来获取锁。自旋其实就是不断的循环进行CAS操作直到能成功替换。所以轻量级锁又叫自旋锁。

另一个博主的解释:

线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;

如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。 自旋锁简单来说就是让线程2在循环中不断CAS

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

下图来源于网络
栈上分配LockRecord如下图: lockrecord中包含了对象的引用地址。
在这里插入图片描述

对象头中markword替换锁记录指针成功之后如下图:
在这里插入图片描述

替换成功之后将锁标志位改为00 表示获取轻量级锁成功。
lockrecord的作用:在这里实现了锁重入,每当同一个线程多次获取同一个锁时,会在当前栈帧中放入一个lockrecord,但是重入是放入的lockrecord关于锁信息的内容为null,代表锁重入。当轻量级解锁时,每解锁一次则从栈帧中弹出一个lockrecord,直到为0.
轻量级锁重入之后如下图:

在这里插入图片描述

当通过CAS自旋获取轻量级锁达到一定次数时,JVM会发生锁膨胀升级为重量级锁。
原因:不断的自旋在高并发的下会消耗大量的cpu资源,所以jvm为了节省cpu资源,进行了锁升级。将等待获取锁的线程都放入一个等待队列中来节省cpu资源。

重量级锁

在重量级锁中将LockRecord对象替换为了monitor对象的实现。主要通过monitorenter和monitorexit两个指令来实现。需要经过系统调用,在并发低的情况下效率会低。
通过openJDK可以查看ObjectMonitor对象的结构:http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/9758d9f36299/src/share/vm/runtime/objectMonitor.hpp

ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL; //拥有当前对象的线程
    _WaitSet      = NULL; //阻塞队列
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //有资格成为候选资源的线程队列
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

关于Monitor详解:https://blog.csdn.net/qq_35044419/article/details/116756409

几种锁状态优缺点对比

在这里插入图片描述

总结

综上,我们发现偏向锁,轻量级锁(又称自旋锁或无锁),重量级锁都是synchronized锁锁实现中锁经历的几种不同的状态。
三种锁状态的场景总结:

  • 只有一个线程进入临界区 -------偏向锁
  • 多个线程交替进入临界区--------轻量级锁
  • 多个线程同时进入临界区-------重量级锁
     

 

 

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 非常感谢您的问题。synchronized锁升级过程是Java中的优化技术之一,它可以减少的竞争,提高程序的执行效率。在Java中,每个对象都有一个(也称为内置或监视器),线程在执行同步代码块时会获取该对象的,并在代码块执行完成后释放该。当多个线程同时访问同一个对象的同步代码块时,就会出现竞争的情况,影响程序的执行效率。 在Java6之前,synchronized的实现存在较大的性能问题,当多个线程竞争同一个时,会导致线程频繁地进入和退出阻塞状态,增加了线程切换的开销,降低了程序的执行效率。为了解决这个问题,Java6引入了偏向锁轻量级重量级等优化技术,并采用锁升级的方式在不同之间进行转换,从而提高程序的执行效率。 具体来说,当一个线程第一次进入同步代码块时,会先尝试获取对象的偏向锁,如果对象没有被其他线程访问过,那么当前线程会获取到该对象的偏向锁,并将当前线程ID记录在对象头中,以后该线程每次进入同步代码块时都无需重新获取,直接使用偏向锁即可。如果有其他线程访问了该对象,那么当前线程会尝试将偏向锁升级为轻量级轻量级是一种自旋,它采用CAS(Compare And Swap)指令来保证线程的原子性,避免了线程阻塞和唤醒的开销。如果当前线程获取轻量级成功,那么就可以在自旋过程中执行同步代码,避免了线程阻塞和唤醒的开销。如果轻量级获取失败,那么当前线程就会尝试将轻量级锁升级重量级,此时当前线程会阻塞等待其他线程释放该对象的,直到获取到重量级才能执行同步代码。 总之,synchronized的升级过程是Java中的优化技术之一,它可以提高程序的执行效率,减少的竞争。 ### 回答2: synchronized是Java中用于保证多个线程访问同一个共享资源时的线程安全的关键字。当一个线程想要执行synchronized修饰的同步代码块时,需要先获取对象的,如果被其他线程占用,则当前线程会被阻塞,直到获取到为止。 synchronized锁升级过程分为偏向锁轻量级重量级三个阶段。 首先,在没有竞争的情况下,将被优化为偏向锁偏向锁的作用是,当一个线程获取到时,会在对象的头部标记记录该线程的ID。在下次该线程再次申请时,无需再次进行同步操作,直接获取即可。 然后,如果有多个线程竞争同一个偏向锁将转为轻量级。这时每个线程都会在自己的线程栈帧中保存的副本。线程会通过CAS(比较并交换)来进行的获取和释放,而不再阻塞线程。 最后,如果多个线程仍然竞争同一个轻量级将升级为重量级重量级的实现是利用操作系统提供的互斥量机制,当一个线程获取后,其他线程将被阻塞,直到持有的线程释放的升级过程多线程环境下进行,根据的状态切换来提高并发效率。通过合理地选择的类型以及的级别,可以更好地平衡性能与安全性之间的关系。 ### 回答3: synchronized锁升级过程是指在Java中保证多线程访问同步代码时的一种优化机制。其主要目的是提高多线程并发访问共享资源时的性能和效率。 当一个线程尝试进入同步代码块时,会先尝试获取对象的无状态。如果成功获取无状态,则可以直接执行同步代码,并将对象标记为偏向锁。这是的第一级别,也是最轻量级。如果在此时另一个线程也想要进入同步代码,就会造成竞争。 如果存在竞争,偏向锁就会升级为轻量级轻量级是通过在对象头中的标识字段中记录指向线程栈中记录的指针来实现的。如果线程竞争太激烈,轻量级就会升级为重量级重量级是指同步代码块被多个线程访问时,会将线程阻塞并等待释放。重量级采用操作系统的互斥量实现,所以比较耗时和耗资源。 在锁升级过程中,的状态会从无状态到偏向锁,再到轻量级,最后到重量级。在逐步升级的过程中,的开销也会逐渐增加。 需要注意的是,在JDK 6之后,引入了消除和膨胀机制。消除指的是JVM在编译器优化时发现某些代码分支中不存在线程竞争时,会去除相应的操作;膨胀指的是JVM会根据竞争情况,将轻量级锁升级重量级。 综上所述,synchronized锁升级过程是为了提高多线程并发访问同步代码时的性能和效率。通过从无状态到偏向锁,再到轻量级,最后到重量级的升级过程,JVM可以根据竞争情况选择最适合的状态,以实现最佳的性能和资源利用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值