synchronized的锁优化是怎么处理的?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了synchronized的锁优化是怎么处理的?相关的知识,希望对你有一定的参考价值。

同步性能在JDK 1.5中相对较低,但经过后续版本的各种优化迭代,其性能也得到了前所未有的提升。 上一篇我们讲了通过锁扩展来提升synchronized性能,但这只是“众多”synchronized性能优化方案中的一个,所以这篇文章就来看看synchronized的核心优化方案。 让我们回顾一下锁扩展对同步性能的影响。 所谓锁扩容,是指从无锁到有偏锁,再到轻量级锁,最后到重量级锁的同步升级过程。 称为锁扩展,也称为锁。 升级。 在JDK 1.6之前,synchronized是重量级锁,意味着synchronized在释放和获取锁的时候会从用户态转换到内核态,转换效率比较低。 

但是有了锁扩展机制,synchronized状态有了更多的无锁、偏向锁、轻量级锁。 这时候在进行并发操作的时候,大部分场景都不需要从用户态转换到内核态。 这大大提高了同步的性能。 很多人都知道synchronized中锁扩展的机制,但对接下来的3个优化却知之甚少。 这样会错过面试的机会。 那我们就把这3个优化拿出来,在本文中分别说一下。 锁消除是指在某些情况下,如果JVM虚拟机检测不到某段代码被共享和竞争的可能性,就会消除该段代码所属的同步锁,从而提高性能 该程序。

 锁消除的基础是逃逸分析的数据支持,比如StringBuffer的append()方法,或者Vector的add()方法。 在很多情况下,可以进行锁消除,比如下面的代码: 从上面的结果可以看出,我们之前写的线程安全且加锁的StringBuffer对象在字节码生成后被替换为一个解锁的不安全的StringBuilder对象 . 原因是 StringBuffer 变量属于局部变量,不会派生自它。 方法逃逸,所以这时候我们可以使用锁消除(不加锁)来加速程序的运行。

参考技术A 应该根据锁优化的功能,还有其中的原理处理,了解这方面的技术,利用科技原理处理这样的事情。 参考技术B 利用锁扩展机制进行更新,而且这样可以有更多的状态。这样大大的提高了运行的效率。 参考技术C 就是对系统内部进行修改,对代码进行修改,对安防系统进行修改,这样就完成了锁优化。

synchronize原理

synchronized的锁的原理
两个重要的概念:一个是对象头,另一个是monitor。

Java对象头
在Hotspot虚拟机中,对象在内存中的布局分为三块区域:对象头(Mark Word、Class Metadata Address)、实例数据和对齐填充;Java对象头是实现synchronized的锁对象的基础。一般而言,synchronized使用的锁对象是存储在Java对象头里。它是轻量级锁和偏向锁的关键。

Mark Word
Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的
锁、偏向线程 ID、偏向时间戳等等。Java对象头一般占有两个机器码(在32位虚拟机中,1个机器码等于4字节,
也就是32bit)。

 

Class Metadata Address
类型指针,即是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

Array length
如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据。

Monitor
Monitor是一个同步工具,它内置于每一个Object对象中,相当于一个许可证。拿到许可证即可以进行操作,没有拿到则需要阻塞等待。

在hotspot虚拟机中,通过ObjectMonitor类来实现monitor。

 

synchronized锁的优化
jdk1.6以后对synchronized的锁进行了优化,引入了偏向锁、轻量级锁,锁的级别从低到高逐步升级: 

无锁->偏向锁->轻量级锁->重量级锁

自旋锁与自适应自旋
线程的挂起和恢复会极大的影响开销。并且jdk官方人员发现,很多线程在等待锁的时候,在很短的一段时间就获得了锁,所以它们在线程等待的时候,并不需要把线程挂起,而是让他无目的的循环,一般设置10次。这样就避免了线程切换的开销,极大的提升了性能。

而适应性自旋,是赋予了自旋一种学习能力,它并不固定自旋10次一下。他可以根据它前面线程的自旋情况,从而调整它的自旋,甚至是不经过自旋而直接挂起。

锁消除
对不会存在线程安全的锁进行消除。

锁粗化
如果jvm检测到有一串零碎的操作都对同一个对象加锁,将会把锁粗化到整个操作外部,如循环体。

偏向锁
多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让其获得锁的代价更低而引入了偏向锁。

当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。

如果测试成功,表示线程已经获得了锁。

如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成01(表示当前是偏向锁)。

如果没有设置,则使用CAS竞争锁。

如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

轻量级锁
引入轻量级锁的主要目的是在多线程竞争不激烈的情况下,通过CAS竞争锁,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

重量级锁
重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。

锁升级
偏向锁升级轻量级锁:当一个对象持有偏向锁,一旦第二个线程访问这个对象,如果产生竞争,偏向锁升级为轻量级锁。

轻量级锁升级重量级锁:一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

wait和notify的原理
调用wait方法,首先会获取监视器锁,获得成功以后,会让当前线程进入等待状态进入等待队列并且释放锁。

当其他线程调用notify后,会选择从等待队列中唤醒任意一个线程,而执行完notify方法以后,并不会立马唤醒线程,原因是当前的线程仍然持有这把锁,处于等待状态的线程无法获得锁。必须要等到当前的线程执行完按monitorexit指令以后,也就是锁被释放以后,处于等待队列中的线程就可以开始竞争锁了。

wait和notify为什么需要在synchronized里面?
wait方法的语义有两个,一个是释放当前的对象锁、另一个是使得当前线程进入阻塞队列,而这些操作都和监视器是相关的,所以wait必须要获得一个监视器锁。

而对于notify来说也是一样,它是唤醒一个线程,既然要去唤醒,首先得知道它在哪里,所以就必须要找到这个对象获取到这个对象的锁,然后到这个对象的等待队列中去唤醒一个线程。

原文:https://blog.csdn.net/u011212394/article/details/82228321

以上是关于synchronized的锁优化是怎么处理的?的主要内容,如果未能解决你的问题,请参考以下文章

synchronized和Reentrantlock的区别

synchronized和Reentrantlock的区别

Synchronized优化

图解 synchronized 的锁升级机制

synchronized你到底锁住的是谁?

Java synchronized原理