Java的锁优化

最近在学习jvm,在看到周志明老师的深入理解Java虚拟机这本书的同时,将自己的所学记录到博客,与大家一起分享。
在jdk1.6中加入大量的锁优化技术,比如适应性自旋锁,锁消除,锁粗化,轻量级锁,偏向锁等等,这些技术都是为了提高程序的执行效率

自旋锁和适应性自旋锁

在线程遇到阻塞时(多线程共同竞争资源时),部分线程会挂起,之后再恢复线程,这部分操作都会在内核中完成,由此会给系统的并发性能带来很大的压力。虚拟机的开发团队注意到其实大部分的共享数据的锁定状态并不会持续很长时间,由此就有了自旋锁。
在线程竞争资源时发现该资源已经被占用,自旋锁就起作用了,这时线程并不会马上挂起,而是在原地自旋等待资源释放。

优点

这种实现方式使的线程不需要切换状态,避免了线程切换的开销

缺点

优点虽然很好,但是它还是要占用处理器时间的,如果长时间得不到锁,就会一直自旋,这样只会白白浪费处理器资源,由此就有了自适应自旋锁
自适应自旋锁的自旋时间是不固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态决定。这样就避免了长时间等待得不到资源的例子,而白白浪费时间,像这种情况会直接挂起线程

锁消除

锁消除是指如果被虚拟机认定为要求同步,却不可能存在竞争的代码,消除他们的锁。锁消除主要的判断依据为逃逸分析(如果判断在一段代码中,堆上所有数据都不会逃逸出去而被其他线程访问,那么就可以吧他们当做是栈上数据对待,认为他们是私有,无需加锁)

锁粗化

是针对一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,这样的话,即使没有线程竞争,频繁的互斥同步操作也会有不必要的性能消耗,因为每次都需要尝试取到锁。对于这种情况,直接粗化,将锁加载一整段代码上

Java对象头

Java对象头里的Mark Word里默认存储对象的HashCode,分代年龄和锁标记位。32位JVM的Mark Word的默认存储结构如下:
| |25 bit |4bit |1bit 是否是偏向锁| 2bit锁标志位|
|:–|–:|
无锁状态 对象的hashCode 对象分代年龄 0 01

状态25bit4bit1bit是否是偏向锁2bit锁标志位
无锁状态对象的hashCode对象分代年龄001

在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下4种数据:
这里写图片描述

偏向锁

偏向锁是jdk1.6中引入的一项锁优化,他的目的是消除数据在吴竞争情况下的同步原语,进一步提高程序的运行性能。
先看一下偏向锁的流程
这里写图片描述
当锁对象第一次被线程获取的时候,虚拟机会将Markword中标志位记录为01,偏向标志记录为1,同时使用cas操作获取线程Id(得到该锁的线程)记录到对象的Markword中,如果操作成功,测以后该线程以后每次进入这个锁的同步块时,虚拟机都不用做如何的同步操作直到撤销偏向为止。如果有新的线程在访问同步块,即使两个没有冲突,偏向标志也会被撤销。
进一步了解偏向锁
偏向锁的释放不需要做任何事情,这也就意味着加过偏向锁的MarkValue会一直保留偏向锁的状态,因此即便同一个线程持续不断地加锁解锁,也是没有开销的。

另一方面,偏向锁比轻量锁更容易被终结,轻量锁是在有锁竞争出现时升级为重量锁,而一般偏向锁是在有不同线程申请锁时升级为轻量锁,这也就意味着假如一个对象先被线程1加锁解锁,再被线程2加锁解锁,这过程中没有锁冲突,也一样会发生偏向锁失效,不同的是这回要先退化为无锁的状态,再加轻量锁,如图:
这里写图片描述

轻量级锁

轻量锁是在1.6时加入的,他并不是重量级锁的代替品,他的本意是在没有多线程竞争的前提下,减少传统重量级锁使用操作系统互斥量产生的性能消耗
在上面偏向锁中其实已经介绍了轻量级锁是如果出现的,当线程处于偏向锁时,有新的进程在竞争资源时,偏向锁取消,这时候开始自旋,如果自旋失败,长时间得不到锁,这时候锁就开始膨胀成为重量级锁,并且线程挂起等待唤醒

  • 轻量级锁加锁过程
    线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间(锁记录Lock Record),并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,则自旋获取锁,当自旋获取锁仍然失败时,表示存在其他线程竞争锁(两条或两条以上的线程竞争同一个锁),则轻量级锁会膨胀成重量级锁。

  • 轻量级锁解锁
    轻量级解锁时,会使用原子的CAS操作来将Displaced Mark Word替换回到对象头,如果成功,则表示同步过程已完成。如果失败,表示有其他线程尝试过获取该锁,则要在释放锁的同时唤醒被挂起的线程。下图是两个线程同时争夺锁,导致锁膨胀的流程图
    这里写图片描述
    这里写图片描述

重量级锁

重量锁在JVM中又叫对象监视器(Monitor),它很像C中的Mutex,除了具备Mutex互斥的功能,它还负责实现了Semaphore的功能,也就是说它至少包含一个竞争锁的队列,和一个信号阻塞队列(wait队列),前者负责做互斥,后一个用于做线程同步。

参考

周志明的<深入理解Java虚拟机>
Java锁优化

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值