java为什么持续下滑,Java面试必考问题:什么是自旋锁

互斥锁的系统开销

前文《Java面试必考问题:如何理解关键字synchronized 》我们介绍了 synchronized 关键字给对象加锁是互斥锁,用来实现线程同步。互斥同步对性能最大的影响是线程阻塞,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统性能带来了很大压力。

138e0f2743ad8bb5144640a4861a77bc.png

互斥锁的线程切换

互斥同步会有两次线程上下文切换的成本:

当线程加锁失败时,内核会把线程的状态从 “运行” 状态设置为 “睡眠” 状态,然后把 CPU 切换给其他线程运行;

当锁被释放时,之前 “睡眠” 状态的线程会变为 “就绪” 状态,然后内核会在合适的时间,把 CPU 切换给该线程运行。

上下文切换需要几十纳秒到几微秒之间,如果锁住的代码执行时间极短,那花在两次上下文切换的时间就会远多于锁住代码的执行时长。

不做线程切换的自旋锁

虚拟机的开发团队也注意到在很多应用中,共享数据的锁定状态通常只会持续很短的一段时间,为了这么短的时间去挂起和恢复线程很不值得。于是他们想到了一种降低线程切换的系统开销的办法。

在多核处理器的系统中,可以让后面请求锁的那个线程先等一下,暂不需要放弃CPU的执行时间,看当前持有锁的线程是否会很快释放掉。如果等一会就能获得锁,就避免了线程切换的开销了。

为了让后面的线程等待,我们只需让线程执行一个忙循环(busy loop),即所谓的自旋锁(spin lock)。自旋锁是一种比较简单的锁,一直自旋,利用 CPU 周期,直到锁可用为止,是通过自旋操作减少CPU切换以及恢复现场导致的消耗。

b0035a5ba58644932254a2eed9ebf320.png

自旋锁与非自旋锁(互斥锁)的区别

自旋锁在JDK 1.4.2中就已经引入,只不过默认是关闭的,可以使用 -XX:+UseSpinning 参数来开启,在JDK 1.6中就已经改为默认开启了。

自旋等待本身虽然避免了线程切换的开销,但它还是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时间很长,那么自旋的线程只会白白消耗处理器资源,反而会带来性能上的浪费。因此,自旋等待的时间必须要有一定的限度。

如果自旋超过了限定的次数仍然没有成功获得锁,就应当挂起线程了。自旋次数的默认值是10次,用户可以使用参数 -XX:PreBlockSpin 来更改。

另外注意的是:在单核 CPU 上,自旋锁是无法使用的,因为一个自旋的线程永远不会放弃 CPU。其他占用锁的线程也没有机会执行代码去释放锁。

自适应的自旋锁

在JDK 1.6中引入了自适应的自旋锁。自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。

如果虚拟机认为自旋很有可能成功获得锁,那么它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那么虚拟机就可能直接省略掉自旋过程,以避免浪费处理器资源。

有了自适应自旋,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,性能就会越来越好。

我会持续更新关于物联网、云原生以及数字科技方面的文章,用简单的语言描述复杂的技术,也会偶尔发表一下对IT产业的看法,欢迎大家关注,谢谢。

我会持续更新关于物联网、云原生以及数字科技方面的文章,用简单的语言描述复杂的技术,也会偶尔发表一下对IT产业的看法,欢迎大家关注,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值