java 代码块锁_java 中 synchronized 代码块的实现,以及 jdk1.6 对锁的优化

被 synchronized 关键字修饰的代码块在被编译成字节码的时候会在该代码块的开头和结尾分别插入 monitorenter 和 monitorexit 指令。任何对象都有一个 monitor 与之关联,当一个 monitor 被持有后,它将处于锁定状态。线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁。虚拟机在执行这两个指令的时候会检查对象的锁状态是否为空或当前线程是否已经拥有该对象锁,如果是,则将对象锁的计数器加 1 直接进入同步代码执行。如果不是,当前线程就要阻塞等待,直到锁释放。也就是说 synchronized 是可重入锁。

jdk1.6 对锁的优化

无锁–>偏向锁–>轻量级锁–>重量级锁,需要注意的是锁可以升级,不可以降级

为了减少获得锁和释放锁所带来的性能消耗,1.6 中出现了轻量级锁,偏向锁,锁消除,适应性自旋锁,锁粗化,自旋锁在 1.4 就有,只不过默认是关闭的,jdk1.6 是默认开启的,这些操作都是为了在线程之间更高效的的共享数据,解决竞争问题,主要优化 synchronized 的获取锁和释放锁的性能问题

理解轻量级锁,必须从 HotSpot 虚拟机的对象头的内存布局来介绍,HotSpot 虚拟机的对象头由两部分组成,第一部分是存储对象自身运行时的数据,如哈希码,GC 分代年龄,锁标记位等,官方称为 MarkWord,它是实现轻量级锁和偏向锁的关键。另外一部分用于存储指向方法区对象类型数据的指针,如果是数组对象的话 还会有一个额外的部分用于存储数组长度。

在代码进入同步块的时候,如果同步对象没有被锁定,虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(LockRecord)的空间。

然后虚拟机将使用 CAS 操作尝试将对象头中的 MarkWord 更新为指向当前线程 LockRecord 的指针,如果这个更新成功了,那么这个线程就拥有了这个对象的锁,并且将 MarkWord 中的标记位改为 00,即表示该对象处于轻量级锁状态

如果这个状态更新失败了,虚拟机将会检查对象中的 markWord 是否指向当前线程的栈帧,如果是,直接进入同步代码块执行,如果不是,说明有线程竞争。如果有两条以上的线程在抢占资源,那轻量级锁就不再有效,要膨胀为重量级锁,锁的状态更改为 10,MarkWord 中存储的就是指向重量级锁的指针,后面等待的锁就要进入阻塞状态。

轻量级锁性能的提升是相对于重量级锁来说的,在重量级锁中一个线程得到了锁其余线程会进入阻塞状态,而 Java 中的线程是映射到操作系统的原生线程上的,线程的阻塞需要从用户态切换到核心态,中间涉及到中断,会消耗大量时间。轻量级锁就是为了避免用户态和核心态的切换而产生的,当一个线程发现目标资源被占用后会原地自旋等待上一个线程结束。多个线程竞争锁的情况下轻量级锁会转换为重量级锁

偏向锁:轻量级锁的引入是为了提升在没有线程竞争情况下执行同步代码的效率的。那么还有一种特殊的情况就是,始终只有一个线程在执行同步块,在这种情况下,即使使用轻量级锁,也会需要多个 CAS 操作,而 CAS 操作由于引入了 volatile 关键字会引起 cache 一致性操作,导致延时,降低效率。当开启了偏向锁功能,当代码进入同步块的时候,虚拟机会检查当前线程是否处于无锁状态且标记位为 0 没有偏向锁,那么线程就会使用 CAS 操作把获取到的这个锁的线程 ID 记录到对象的 MarkWord 中,如果操作成功,那么持有锁的线程以后在每次进入这个锁的相关同步块的时候,虚拟机不再使用任何同步操作,只坚持对象头中是否为当前线程的 ID,如果是直接省去了 CAS 操作。当另外一个线程获取该对象的锁的时候,偏向模式就会宣告结束,根据锁当前的状态,撤销偏向锁后恢复无锁状态或转换到轻量级锁状态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值