加入收藏 | 设为首页 | 会员中心 | 我要投稿 辽源站长网 (https://www.0437zz.com/)- 云专线、云连接、智能数据、边缘计算、数据安全!
当前位置: 首页 > 运营中心 > 建站资源 > 优化 > 正文

详解Java锁机制:看完你就明白的锁系列之锁的状态

发布时间:2019-10-18 00:47:42 所属栏目:优化 来源:java互联网高级架构
导读:看完你就会知道,线程如果锁住了某个资源,致使其他线程无法访问的这种锁被称为悲观锁,相反,线程不锁住资源的锁被称为乐观锁,而自旋锁是基于 CAS 机制实现的,CAS又是乐观锁的一种实现,那么对于锁来说,多个线程同步访问某个资源的流程细节是否一样呢?

无锁的特点就是在循环内进行修改操作,线程会不断的尝试修改共享资源,直到能够成功修改资源并退出,在此过程中没有出现冲突的发生,这很像我们在之前文章中介绍的 CAS 实现,CAS 的原理和应用就是无锁的实现。无锁无法全面代替有锁,但无锁在某些场合下的性能是非常高的。

偏向锁

Hotspot 的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,还存在锁由同一线程多次获得的情况,偏向锁就是在这种情况下出现的,它的出现是为了解决只有在一个线程执行同步时提高性能。

可以从对象头的分配中看到,偏向锁要比无锁多了线程ID 和 epoch,当一个线程访问同步代码块并获取锁时,会在对象头和栈帧的记录中存储线程的ID,等到下一次线程在进入和退出同步代码块时就不需要进行 CAS 操作进行加锁和解锁,只需要简单判断一下对象头的 Mark Word 中是否存储着指向当前线程的线程ID,判断的标志当然是根据锁的标志位来判断的。

偏向锁的获取过程

  1. 访问 Mark Word 中偏向锁的标志是否设置成 1,锁的标志位是否是 01 --- 确认为可偏向状态。
  2. 如果确认为可偏向状态,判断当前线程id 和 对象头中存储的线程 ID 是否一致,如果一致的话,则执行步骤5,如果不一致,进入步骤3
  3. 如果当前线程ID 与对象头中存储的线程ID 不一致的话,则通过 CAS 操作来竞争获取锁。如果竞争成功,则将 Mark Word 中的线程ID 修改为当前线程ID,然后执行步骤5,如果不一致,则执行步骤4
  4. 如果 CAS 获取偏向锁失败,则表示有竞争(CAS 获取偏向锁失败则表明至少有其他线程曾经获取过偏向锁,因为线程不会主动释放偏向锁)。当到达全局安全点(SafePoint)时,会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否存活(因为可能持有偏向锁的线程已经执行完毕,但是该线程并不会主动去释放偏向锁),如果线程不处于活动状态,则将对象头置为无锁状态(标志位为01),然后重新偏向新的线程;如果线程仍然活着,撤销偏向锁后升级到轻量级锁的状态(标志位为00),此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁。
  5. 执行同步代码

偏向锁的释放过程

偏向锁的释放过程可以参考上述的步骤4 ,偏向锁在遇到其他线程竞争锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为01)或轻量级锁(标志位为00)的状态。

关闭偏向锁

偏向锁在Java 6 和Java 7 里是默认启用的。由于偏向锁是为了在只有一个线程执行同步块时提高性能,如果你确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。

关于 epoch

真正理解 epoch 的概念比较复杂,这里简单理解,就是 epoch 的值可以作为一种检测偏向锁有效性的时间戳

轻量级锁

轻量级锁是指当前锁是偏向锁的时候,被另外的线程所访问,那么偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。

加锁过程

在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为 01 状态,是否为偏向锁为 0 ),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 的拷贝,然后拷贝对象头中的 Mark Word 复制到锁记录中。

详解Java锁机制:看完你就明白的锁系列之锁的状态

拷贝成功后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针,并将 Lock Record里的 owner 指针指向对象的 Mark Word。

如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为 00 ,表示此对象处于轻量级锁定状态。

详解Java锁机制:看完你就明白的锁系列之锁的状态

如果这个更新操作失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为 10 ,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。

重量级锁

重量级锁也就是通常说 synchronized 的对象锁,锁标识位为10,其中指针指向的是 monitor 对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如 monitor 可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。

详解Java锁机制:看完你就明白的锁系列之锁的状态

上图简单描述多线程获取锁的过程,当多个线程同时访问一段同步代码时,首先会进入 Entry Set当线程获取到对象的 monitor 后进入 The Owner 区域并把 monitor 中的 owner 变量设置为当前线程,同时 monitor 中的计数器count 加1,若线程调用 wait() 方法,将释放当前持有的 monitor,owner变量恢复为 null,count自减1,同时该线程进入 WaitSet 集合中等待被唤醒。若当前线程执行完毕也将释放 monitor (锁)并复位变量的值,以便其他线程进入获取monitor(锁)。

(编辑:辽源站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

推荐文章
    热点阅读