深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁

JDK1.6为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在JDK1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率.

  • Java中的锁有几种状态:无锁 → 偏向锁 → 轻量级锁 → 重量级锁

无锁状态

程序不会有锁的竞争。那么这种情况我们不需要加锁,所以这种情况下对象锁状态为无锁。

偏向锁

偏向锁,顾名思义,它会偏向于第一个访问锁的线程

  • 如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。线程第二次到达同步代码块时,会判断此时持有锁的线程是否就是自己,如果是则正常往下执行。由于之前没有释放锁,这里也就不需要重新加锁。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。
  • 如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。偏向锁通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。一旦有第二个线程加入锁竞争,偏向锁就升级为轻量级锁(自旋锁)。升级为轻量级锁的时候需要撤销偏向锁,撤销偏向锁的时候会导致STW(stop the word)操作;

锁竞争:如果多个线程轮流获取一个锁,但是每次获取锁的时候都很顺利,没有发生阻塞,那么就不存在锁竞争。只有当某线程尝试获取锁的时候,发现该锁已经被占用,只能等待其释放,这才发生了锁竞争。

轻量级锁(自旋锁)

自旋锁:自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。

  • 在轻量级锁状态下继续锁竞争,如果成功就成功获取轻量级锁。否则进入锁膨胀阶段,没有抢到锁的线程将自旋,即不停地循环判断锁是否能够被成功获取。长时间的自旋操作是非常消耗资源的,一个线程持有锁,其他线程就只能在原地空耗CPU,执行不了任何有效的任务,这种现象叫做忙等(busy-waiting)。如果锁竞争情况严重,某个达到最大自旋次数的线程,会将轻量级锁升级为重量级锁
  • 自旋锁:竞争锁失败的线程,并不会真实的在操作系统层面挂起等待,而是JVM会让线程做几个空循环(基于预测在不久的将来就能获得),在经过若干次循环后,如果可以获得锁,那么进入临界区,如果还不能获得锁,才会真实的将线程在操作系统层面进行挂起。这样的好处就是快,坏处就是消耗cpu资源。

轻量级锁不会自旋,另一个线程一旦CAS竞争轻量级锁失败,直接进入锁的膨胀。锁膨胀会有自旋操作,但最终在java层面看到的锁状态都会是重量级锁。所以,轻量级锁在加锁过程中是没有自旋的。自旋发生在轻量级锁膨胀为重量级锁的过程中。
相反,重量级锁在加锁的过程中,为了避免直接park线程,会有自适应自旋操作,来挽救线程被park。

重量级锁

  • 当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起,等待将来被唤醒。在JDK1.6之前,synchronized直接加重量级锁,很明显现在得到了很好的优化。

重量级锁的特点:其他线程试图获取锁时,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程。

锁的优缺点对比

优点缺点适用场景
偏向锁加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。如果线程间存在锁竞争,会带来额外的锁撤销的消耗。适用于只有一个线程访问同步块场景。
轻量级锁竞争的线程不会阻塞,提高了程序的响应速度。如果始终得不到锁竞争的线程使用自旋会消耗CPU。追求响应时间。同步块执行速度非常快。
重量级锁线程竞争不使用自旋,不会消耗CPU。线程阻塞,响应时间缓慢。追求吞吐量。同步块执行速度较长。

锁升级场景

场景1: 经常只有某一个线程来加锁。

  • 加锁过程:也许获取锁的经常为同一个线程,这种情况下为了避免加锁造成的性能开销,加偏向锁
  • 偏向锁的执行流程如下:
    • 1、线程首先检查该对象头的线程ID是否为当前线程;
    • 2、A:如果对象头的线程ID和当前线程ID一致,则直接执行代码;B:如果不是当前线程ID则使用CAS方式替换对象头中的线程ID,如果使用CAS替换不成功则说明有线程正在执行,存在锁的竞争,这时需要撤销偏向锁,升级为轻量级锁。
    • 3、如果CAS替换成功,则把对象头的线程ID改为自己的线程ID,然后执行代码。
    • 4、执行代码完成之后释放锁,把对象头的线程ID修改为空。

场景2: 有线程来参与锁的竞争,但是获取锁的冲突时间很短。

  • 当开始有锁的竞争了,那么偏向锁就会升级到轻量级锁;
  • 线程获取锁出现冲突时,线程必须做出决定是继续在这里等,还是先去做其他事情,等会再来看看,而轻量级锁的采用了继续在这里等的方式。当发现有锁竞争,线程首先会使用自旋的方式循环在这里获取锁,因为使用自旋的方式非常消耗CPU。当一定时间内通过自旋的方式无法获取到锁的话,那么锁就开始升级为重量级锁了。

场景3: 有大量的线程参与锁的竞争,冲突性很高。

  • 当获取锁冲突多,时间越长的时候,线程肯定无法继续在这里死等了,所以只好先挂起,然后等前面获取锁的线程释放了锁之后,再开启下一轮的锁竞争,而这种形式就是我们的重量级锁。

你知道的越多,你不知道的越多。
有道无术,术尚可求,有术无道,止于术。
如有其它问题,欢迎大家留言,我们一起讨论,一起学习,一起进步

  • 35
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
这是一个关于Java升级过程的问题。我会尽力解答,但是请注意我的回答可能会涉及一些技术术语。 Java中的分为四种级别:无锁偏向锁轻量级重量级。这些级别是根据竞争的线程数量和状态的不同而定义的。 1. 无锁:在无锁状态下,线程可以直接访问共享数据,不需要进行任何同步操作。这种情况只有在并发访问非常低的情况下才会出现,因为在高并发情况下,不同的线程很可能会同时访问同一块数据,这就需要进行同步处理。 2. 偏向锁:当只有一个线程访问共享数据时,可以使用偏向锁来提高性能。偏向锁是一种优化技术,它会记录下最后一个获取的线程,并在下一次访问时直接使用该线程持有的。这样可以减少的竞争,提高程序的性能。当有第二个线程访问同一块数据时,偏向锁就会升级轻量级。 3. 轻量级轻量级是一种比偏向锁更高级的机制。当两个线程访问同一块数据时,偏向锁就会升级轻量级轻量级使用CAS(Compare And Swap)操作来尝试获取,如果成功就直接持有,如果失败就会自旋等待。这种机制能够减少线程的上下文切换,提高程序的性能。当自旋次数超过一定值时,轻量级就会升级重量级。 4. 重量级重量级是一种最基本的机制。当轻量级自旋次数超过一定值时,就会升级重量级重量级使用操作系统的互斥量来进行同步操作,这种机制需要进行线程的上下文切换,会降低程序的性能。 以上就是Java升级过程。在实际应用中,应该根据具体的情况选择不同的机制,以达到最优的性能表现。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值