Java锁

锁概念

公平锁与非公平锁

公平锁:线程获取锁的顺序与其申请顺序一致

非公平锁:线程获取锁的顺序并不是按照申请锁顺序,有可能后申请锁的线程先获取锁。

可重入锁与不可重入锁

可重入锁:线程获取锁后,可以重复进入同步代码区,不需要重复申请锁

不可重入锁:线程每次进入同步代码区时,均需要获取锁,即使已经获取过该锁。

悲观锁与乐观锁

悲观锁:总是持有悲观的态度,认为并发冲突一般会发生

乐观锁:总是持有乐观的态度,认为并发冲突一般不会发生

锁消除与锁粗化

锁消除:消除是Java虚拟机在JIT编译期间,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过锁消除,可以节省毫无意义的请求锁时间。

锁粗化:在遇到一连串地对同一锁不断进行请求和释放的操作时,把所有的锁操作整合成对锁的一次请求,从而减少对锁的请求同步次数

自旋锁和自适应自旋

自旋锁:线程尝试获取锁,发现被占用后,不放弃cpu执行时间,进入阻塞状态,而是进入进行循环重试,重试指定次数后,仍未获取锁,则进入阻塞过程。

自适应自旋:重试次数不固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。

Java锁升级

对象内存布局

Java对象在内存中存储的布局可以分为3块区域: 对象头实例数据对齐填充

对象头,分为两个部分,第一个部分存储对象自身的运行时数据,又称为Mark Word,32位虚拟机占32bit,64位虚拟机占64bit。如图所示,不同锁状态下,Mark Word的结构,理解下面要介绍的各种锁,和锁升级过程,都需要先充分了解Mark Word的结构。

第二部分是类型指针,指向类元数据指针,虚拟机通过此指针,确定该对象属于那个类的实例。

偏向锁

引入偏向锁的目的是在没有多线程竞争的前提下,进一步减少线程同步的性能消耗。

  • 偏向锁的获取

    开启偏向锁模式后,锁第一次被线程获取的时候,虚拟机会把对象头中是否为偏向锁的标志位设位1,

    同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中。当有另外一个线程去尝试获取这个锁时, 偏向模式就宣告结束。

    根据锁对象目前是否处于被锁定的状态, 撤销偏向( Revoke Bias) 后恢复到未锁定( 标志位为“01”)或轻量级锁定( 标志位为“00”) 的状态

  • 偏向锁的释放

    偏向锁,并没有显式的锁释放过程,主要依靠锁的批量再偏向(Bulk Rebias)机制实现锁释放。

    该机制的主要工作原理如下:

    • 引入一个概念 epoch, 其本质是一个时间戳 , 代表了偏向锁的有效性,从前文描述的对象头结构中可以看到,

      epoch 存储在可偏向对象的 MarkWord 中。

    • 除了对象中的 epoch, 对象所属的类 class 信息中, 也会保存一个 epoch 值,每当遇到一个全局安全点时, 如果要对 class 进行批量再偏向, 则首先对 class 中保存的 epoch 进行增加操作, 得到一个新的 epoch_new

    • 然后扫描所有持有 class 实例的线程栈,根据线程栈的信息判断出该线程是否锁定了该对象, 仅将epoch_new 的值赋给被锁定的对象中。

    • 退出安全点后, 当有线程需要尝试获取偏向锁时, 直接检查 class中存储的 epoch 值是否与目标对象中存储的 epoch 值相等,如果不相等, 则说明该对象的偏向锁已经无效了, 可以尝试对此对象重新进行偏向操作。

轻量级锁

轻量级锁是相对于重量级锁(Synchrnoized)而言的,本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

  • 轻量级锁的获取
    • 线程进入同步块时,如果此同步对象没有被锁定(即锁标志位为01,是否为偏向锁为0),虚拟机在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的一个Mark Word的copy ,ower设置为当前线程。

    • 然后虚拟机使用CAS操作,尝试将Mark World更新为指向Lock Record的指针,如果更新成功,那么线程拥有了该对象的锁,并且将锁标志位置位00,如图所示

    • 如果只有一个线程尝试获取轻量级锁,会进行自旋等待,一旦有两条以及以上的线程抢占该锁,轻量级锁会升级为重量级锁。

      锁标志位置为10,Mark Word存储的就是指向重量级锁的指针

  • 轻量级锁释放
    • 如果对象的Mark Word仍然指向着线程的锁记录, 那就用CAS操作把对象当前的Mark Word和线程中复制的Displaced Mark Word替换回来, 如果替换成功, 整个同步过程就完成了。如果替换失败, 说明有其他线程尝试过获取该锁,轻量级锁膨胀为重量级锁,那就要在释放锁的同时, 唤醒被挂起的线程。
整个锁升级过程

参考文章

偏向锁,轻量级锁与重量级锁的区别与膨胀

Java中的偏向锁,轻量级锁, 重量级锁解析

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐