Java锁优化

​ 在JDK6中,HotSpot虚拟机对锁进行了大量的优化。众多的锁优化技术包括自旋锁、锁消除、锁粗化、轻量级锁、偏向锁等。

自旋锁

  • JDK6以前,Java虚拟机的锁都是通过互斥来实现的。互斥同步对性能最大的影响是线程的阻塞,线程的阻塞和唤醒需要CPU从用户态转为核心态。
  • 挂起和恢复线程会增加CPU负担,影响并发能力。
  • 同时,很多情况下数据的锁定状态只会持续很短的一段时间,为此挂起和恢复线程并不值得。

自旋锁的机制是:

  • 让线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁。
  • 为了让线程等待,让线程执行一个忙循环(自旋)

自旋锁在JDK1.4.2就已经引入,但默认是关闭的;在JDK6中就已经改为默认开启了

  • 自旋等待不能代替阻塞
    • 自旋等待本身要占用处理器时间
    • 如果锁被占用的时间很短,自旋等待的效果就会非常好
    • 反之,自旋的线程只会消耗处理器资源,而不做任何有价值的工作
    • 因此自旋等待的时间必须有一定的限度
      • 自旋次数的默认值是十次,用户也可以使用参数-XX:PreBlockSpin来自行更改

适应性自旋

  • 在JDK6中对自旋锁的优化,引入了自适应的自旋
  • 自旋的时间不再是固定的了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态决定

锁消除

锁消除指虚拟机即时编译器在运行时,对一些代码要求同步,但是对被检测到不可能存在共享数据竞争的锁进行消除。

锁粗化

  • 原则上,我们在编写代码时,应该将同步块的作用范围限制的尽量小,即只在共享数据的部分进行同步,这样可以减少同步的操作
  • 但是如果对一个对象反复加锁和解锁(如加锁操作出现在循环体中),也会带来不必要的性能损耗。

锁粗化的机制是:如果虚拟机探测到一系列操作都是对同一个对象加锁,则会把加锁同步的范围粗化到这一系列操作之外。

如:

	public static void test() {
        List<String> list = new ArrayList<>();
        //虽然synchronized是在循环里面,但实际上加锁的范围会扩大到循环外
        for (int i=0; i<10; i++) {
            synchronized (Demo.class) {
                list.add(i + "");
            }
        }
        System.out.println(list);
    }

轻量级锁

轻量级锁是JDK6引入的新型锁机制,区别于传统的重量级锁

  • 轻量级锁并不是用来代替重量级锁的
  • 它的设计初衷是在没有多线程竞争的前提下,减少传统重量级锁使用操作系统互斥量产生的性能消耗

轻量级锁的设计依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”

  • 如果没有竞争,轻量级锁便通过CAS操作成功避免了使用互斥量的开销
  • 但如果存在竞争,除了互斥量本身的开销外,还额外发生了CAS操作的开销
  • 因此在有竞争的情况下,轻量级锁比重量级锁更慢
轻量级锁的工作流程
  • 在代码即将进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝
  • 虚拟机将使用CAS操作尝试把对象的Mark Word更新为指向Lock Record的指针
    • 如果这个操作成功了,即代表该线程拥有了这个对象的锁,并且对象Mark Word的锁标志位将转变为“00”,表示此对象出于轻量级锁定状态
    • 如果这个操作失败了,那么意味着至少存在一个线程与当前线程竞争获取该对象的锁
      • 虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧
        • 如果是,说明当前线程已经拥有了这个对象的锁,直接进入同步代码块执行
        • 否则说明这个锁对象已经被其他线程抢占了
        • 如果两个以上的线程争用同一个锁,则轻量级锁升级为重量级锁
          • 锁标志的状态值变为“10”
          • Mark Word中存储的是指向重量级锁(互斥量)的指针
          • 后面等待的线程必须进入阻塞状态

偏向锁

偏向锁是JDK6引入的锁优化措施,目的是消除数据在无竞争情况下的同步原语,进一步提高性能。

偏向锁的机制是:这个锁会偏向第一个获得锁的线程,如果接下来该锁一直没有被其他线程获取,则持有偏向锁的线程永远不需要同步。

偏向锁的工作流程
  • 假设当前虚拟机启用了偏向锁(启用参数-XX:+UserBiased Locking,JDK6起HotSpot虚拟机的默认值),那么当所对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设置为“01”,把偏向模式设置为“1”,表示进入偏向模式
  • 同时使用CAS操作把获取这个锁的线程的ID记录在对象的Mark Word之中
    • 如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作
  • 一旦另一个线程去尝试获取这个锁,偏向模式就会结束
    • 根据锁对象目前是否处于被锁定的状态决定是否撤销偏向(偏向模式设置为“0”)
    • 撤销后标志位恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)状态
    • 后续操作按照轻量级锁去执行

参考文献:《深入理解Java虚拟机》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值