您的位置:首页 > 编程语言 > Java开发

【synchronized底层原理之3】JDK1.6对锁所做的优化

2018-09-12 09:30 701 查看

[b][b]参考网页[/b][/b]

[u]https://www.cnblogs.com/dsj2016/p/5714921.html[/u]
[u]https://www.zhihu.com/question/55075763[/u]

[b][b]JDK1.6对synchronized锁[/b][b]所做的优化[/b][/b]

优化包括自旋锁、锁粗化、锁消除(锁清除)、轻量级锁和偏向锁。
这些都是Java虚拟机层面的优化,对Java程序员是无感知的(Java程序员在程序层面是看不出什么的,但是JVM内部已对synchronized做了优化)。要说有感知的话,就是synchronized在多线程下的性能提高了(JDK1.6相比于JDK1.5),这些都得益于JVM层面锁的优化,就是上面所说的自旋锁、锁粗化、锁清除、偏向锁和轻量级锁。

[b][b]自旋锁[/b][b]:本质上就是[/b][b]CPU空转等待一会儿获取锁的控制权,而不是马上进入阻塞状态[/b][/b]

[b][b]原理[/b][b]--互斥同步->产生阻塞->上下文切换->操作系统级别用户态和核心态的切换,而使用自旋不会产生阻塞,是个有益的补充[/b][/b]

通常情况下,共享数据的锁定状态只持续很短的一段时间,为了这很短的一段时间进行上下文切换并不值得。

[b][b]原理[/b][b]--CPU空转等待获取锁的控制权[/b][/b]

[b]自旋就是[/b][b]不停循环看是否能等到上个线程自己释放锁[/b]。这个问题是基于一个现实考量的:很多拿了锁的线程会很快释放锁。因为一般敏感的操作不会很多。当然这个是一个不能完全确定的情况,[b]只能说总体上是一种优化[/b]。
举个例子就好比一个人要上厕所发现厕所里面有人,他可以:1,等一小会。2,跑去另外的地方上厕所。等一小会不一定能等到前一个人出来,不过如果跑去别的厕所的花费的时间肯定比等一小会结果前一个人出来了长。当然等完了结果那个人没出来还是要跑去别的地方上厕所这是最慢的。

[b][b]优点[/b][b]--高效:不会引起[/b][b]上下文切换[/b][/b]

[b][b]缺点[/b][b]--自旋时间如果过长就会造成CPU资源的浪费[/b][/b]

[b][b]自适应自旋[/b][b]--根据以往经验设置合理的自旋次数、设计合理的自旋时间[/b][/b]

自适应自旋可以根据以往自旋等待时间的经验,计算出一个较为合理的本次自旋等待时间。
比如第一次设置最多自旋10次,结果在自旋的过程中成功获得了锁,那么下一次就可以设置成最多自旋20次。道理是:一个锁如果能够在自旋的过程中被释放说明很有可能下一次也会发生这种事。那么就更要给这个锁某种“便利”方便其不阻塞得锁(毕竟快了很多)。同样如果多次尝试的结果是完全不能自旋等到其释放锁,那么就说明很有可能这个临界区里面的操作比较耗时间。就减小自旋的次数,因为其可能性太小了。

[b][b]锁粗化[/b][b]--JVM扩大加锁的范围[/b][/b]

若有一系列操作,反复地对同一把锁进行上锁和解锁操作,编译器会扩大这部分代码的同步块的边界,从而只使用一次上锁和解锁操作。
试想有一个循环,循环里面是一些敏感操作,有的人就在循环里面写上了synchronized关键字。这样确实没错不过效率也许会很低,因为其频繁地拿锁释放锁。要知道锁的取得(假如只考虑重量级MutexLock)是需要操作系统调用的,从用户态进入内核态,开销很大。于是针对这种情况也许虚拟机发现了之后会适当扩大加锁的范围(所以叫锁粗化)以避免频繁的拿锁释放锁的过程。

[b][b]锁消除[/b][b](锁清除)[/b][b]--JVM去掉多余的锁[/b][/b]

编译器会清除一些使用了同步,但同步块中没有涉及共享数据的锁,从而减少多余的同步。
通过逃逸分析发现其实根本就没有别的线程产生竞争的可能(别的线程没有临界量的引用),而“自作多情”地给自己加上了锁。有可能虚拟机会直接去掉这个锁。

[b][b]轻量级锁[/b][b]:(相对于[/b][b]重量级锁[/b][b])取消[/b][b]互斥同步,[/b][b]使用[/b][b]CAS操作同步[/b][/b]

[b][b]本质[/b][/b]

[b]使用CAS取代互斥同步[/b]。

[b][b]与重量级锁的比较[/b][/b]

重量级锁是一种悲观锁,使用互斥同步来保证线程的安全;
轻量级锁是一种乐观锁,使用CAS操作进行同步。

[b][b]实现原理[/b][/b]

当线程请求锁时,若该锁对象的Mark Word中标志位为01(未锁定状态),则在该线程的栈帧中创建一块名为『锁记录』的空间,然后将锁对象的Mark Word拷贝至该空间;然后通过CAS操作将锁对象的Mark Word指向该锁记录;
若CAS操作成功,则轻量级锁的上锁过程成功;
若CAS操作失败,再判断当前线程是否已经持有了该轻量级锁;若已经持有,则直接进入同步块;若尚未持有,则表示该锁已经被其他线程占用,此时轻量级锁就要膨胀成重量级锁。

[b][b]适用场景[/b][/b]

轻量级锁比重量级锁性能更高的前提是,在轻量级锁被占用的整个同步周期内,不存在其他线程的竞争。若在该过程中一旦有其他线程竞争,那么就会膨胀成重量级锁,从而除了使用互斥量以外,还额外发生了CAS操作,此时耗时更长,会更慢。

[b][b]偏向锁[/b][b]:(相对于轻量级锁)完全取消同步操作[/b][/b]

[b][b]作用[/b][/b]

偏向锁是为了消除无竞争情况下的同步原语,进一步提升程序性能。

[b][b]与轻量级锁的区别[/b][/b]

轻量级锁是在无竞争的情况下使用CAS操作来代替互斥量的使用,从而实现同步;而偏向锁是在无竞争的情况下完全取消同步。

[b][b]与轻量级锁的相同点[/b][/b]

它们都是乐观锁,都认为同步期间不会有其他线程竞争锁。

[b][b]原理[/b][/b]

当线程请求到锁对象后,将锁对象的状态标志位改为01,即偏向模式。然后使用CAS操作将线程的ID记录在锁对象的Mark Word中。以后该线程可以直接进入同步块,连CAS操作都不需要。但是,一旦有第二条线程需要竞争锁,那么偏向模式立即结束,进入轻量级锁的状态。

[b][b]优点[/b][/b]

偏向锁可以提高有同步但没有竞争的程序性能。但是如果锁对象时常被多条线程竞争,那偏向锁就是多余的。

[b][b]偏向锁可以通过虚拟机的参数来控制它是否开启[/b][/b]

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  JDK Mark