Java--乐观锁/悲观/偏向锁/轻量级锁/重量级锁

1:乐观锁

乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对数据锁定(这使得多个任务可以并行的对数据进行操作),只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(一般是通过加版本号然后进行比对的方式实现);

特点:乐观锁是一种并发类型的锁,本身不对数据进行加锁通过业务实现锁的功能,不对数据进行加锁就意味着允许多个请求同时访问数据,这种方式大大的提高了并发数据请求的性能。

Java JUC中的atomic包就是乐观锁的一种实现。如 AtomicInteger 通过CAS(Compare And Set)操作实现线程安全的自增。

2:悲观锁

悲观锁是基于一种悲观的态度来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人才可对数据进行加锁,然后才可以对数据进行操作。

特点:可以完全保证操作数据的独占性和正确性,但因其加锁释放锁的过程会造成消耗,所以性能不高;

Java synchronized(JDK1.5前) 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被 block。

乐观锁和悲观锁的最主要区别是有没有加锁。 
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。 
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
 

首先要解释清楚这几种锁的特征和区别,以及运行时候的变化,需要了解Java对象头的一些知识,在Java对象头的Mark Word内就记录着这些不同锁的状态位。另外偏向锁---轻量级锁---重量级锁本文都需要依托synchronize进行理解和分析。另外也要参照网络上很多的资料。

1.对象头:

关于对象头的具体记录,可以参考:上一篇博客(Java内存区域对象的内存布局和访问定位(Header、Instance、Padding))

2.同步的原理:

关于JVM规范对于同步的解释,可以参考这边:monitorentry和monitorexit

1.数据存储的变化---(主要针对Mark Word)

1.1基础状态 

在运行期间,对象头内Mark Word内存储的数据会随着锁标志位的变化而变化,具体分为以下4种(基于32位虚拟机):

这里写图片描述

1.2 c++中的存储结构分析和探究

上图中的Mark Word是一个表格式的展示样式,也是我从虚拟机书中和网络上找来的,但是我还是去搜罗了一下c++中的结构数据,便于下面对于流程的分析。

1.2.1Mark Word例子解析图

下图是截取于markOop.hpp

67行: 指向一个线程的显示偏向锁。

68行:一个匿名偏向锁。

72行:轻量级锁

73行:无锁

74行:重量级锁

75行:GC时使用

1.2.2 markOop中的枚举位解析:

基于上图中的最后3位,下图给出了枚举解释。

我们稍加分析下几个值:

5 == 101 就是上图中的偏向锁。

3 == 11 GC时使用。

3.偏向锁

2.1偏向锁的解释: 

以下摘自《深入理解Java虚拟机》,关于偏向锁的理解,是有点复杂的,我先是把此书内的观点摘抄下来,然后对比着理解。后续如果是蓝色的字体就是摘抄的书中的原话。借此来理解和分析。

锁偏向是一种针对加锁操作的优化手段。它的核心思想是:如果一个线程获得了锁,那么锁就进入偏向模式。当这个线程再次请求锁时,无须再做任何同步操作。这样就节省了大量有关锁申请的操作,从而提高了程序性能。因此,对于几乎没有锁竞争的场合,偏向锁有比较好的优化效果,因为连续多次极有可能是同一个线程请求相同的锁。而对于锁竞争比较激烈的场合,其效果不佳。因为在竞争激烈的场合,最有可能的情况是每次都是不同的线程来请求相同的锁。这样偏向模式会失效,因此还不如不启用偏向锁

  1. 当锁对象第一次被线程获取的时候,虚拟机会把锁对象头中的标志位设置为01,即偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在锁对象的Mark Word中。如果操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作(例如,Locking,Unlocking,以及对Mark Word的update)。
  2. 当有另一个线程去尝试获取到这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定状态,撤销偏向后恢复到未锁定状态或升级为轻量级锁定的状态。

总之,偏向锁应对的是被同一个线程进行访问的情况,此时根本就没有发生并发,故每次加锁都会损失性能。

  1. 偏向,也可以理解为偏心。当锁对象第一次被某个线程访问时,它会在其对象头的 markOop 中记录该线程ID,那么下次该线程再次访问它时,就不需要进行加锁了。 
  2. 但是这中间只要发生了其他线程访问该锁对象的情况,证明这个对象会发生并发,就不能对这个对象再使用偏向锁了,会进行锁的升级。
     

解释一:

1.偏向锁的获取:

我们可以通过在启动JVM的时候加上-XX:-UseBiasedLocking参数来禁用偏向锁(在存在大量锁对象的创建并高度并发的环境下禁用偏向锁能够带来一定的性能优化)。

2.偏向锁的撤销:

 

4:轻量级锁

 轻量级锁的获取当中有个很重要的概念,即线程栈帧中的锁记录(Lock Record),下图取自周志明-虚拟机一书:

Stack中hdr和Lock Record存在的几个关键操作步骤如下图:

 

 

  1. 操作1:虚拟机会在线程栈帧中建立一个Lock Record,displaced hdr用于存储锁对象目前的Mark Word。
  2. 操作2:虚拟机使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针。
  3. 操作3:如果CAS成功了,将owner指向Mark Word(将栈帧中的数据写回Mark Word),代表了这个线程拥有了该对象的锁。
  4. 操作4:CAS执行失败说明期间有线程尝试获得锁并自旋失败,轻量级锁升级为了重量级锁,此时释放锁之后,还要唤醒等待的线程

 基本流程分析:

 

这里写图片描述

一个还不错的比喻:

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值