Java synchronize原理和锁优化

什么是锁优化?

JDK1.5为了解决synchronize效率问题,出现了Lock锁。(Lock锁源码分析看这篇文章Java8 Lock锁详解(AQS,CAS))JDK1.6对synchronize进行了大量优化,让synchronize能够和和Lock锁性能不相上下。

synchronize锁优化策略

自适应自旋锁
自旋就是在获取锁失败之后循环进行尝试,自适应是指动态的对循环次数进行调整。
举个例子,在自适应时,如果尝试了5次能够获取到锁,且尝试5次的时间比阻塞的时间段,则使用10作为自旋次数。如果尝试了很多次依然没获取到锁,则降低锁的自旋次数。自旋对于执行临界区代码较快的比较有效。
线程的阻塞和恢复需要从用户态到内核态的切换。比较耗费时间。而使用自旋锁,则能够避免这种切换。对于

锁消除
通过逃逸分析发现其实根本就没有别的线程产生竞争的可能(别的线程没有临界量的引用),而“自作多情”地给自己加上了锁。有可能虚拟机会直接去掉这个锁。

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

开发中的锁优化策略

减少锁的持有时间
让临界区代码更加集中,加锁的粒度尽量和临界区的大小一致。

锁细化
例如ConcurrentHashMap仅对hash到的桶进行加锁

锁分离
例如读写锁,读操作共享锁,写操作使用排他锁。

synchronize原理

JVM在编译时会在synchronize代码块的入口加上monitorenter命令,在synchronize代码块的出口加上monitorexit命令。
每个对象都有一个monitor监视器,调用monitorenter就是尝试获取这个对象,成功获取到了就将值+1(和Lock锁的实现原理一样)调用monitorexit将值减1。如果是线程重入(递归调用),在将值+1(和Lock锁的实现原理一样),说明synchronize是可重入锁。

对象头的组成

对象类型 组成(32位JVM)
普通对象 32位MarkWord,32位类型指针(类的静态变量指针,对象的属性指针)
数组对象 32位MarkWord,32位类型指针(类的静态变量指针,对象的属性指针),32位数组长度

类型指针主要用来进行寻址
MarkWorld主要用来进行对象状态标记

锁膨胀(锁升级)

锁从轻量级锁升级为重量级锁的过程称为锁膨胀。
MarkWord(标记字在不同锁状态下的组成)
Java synchronize原理和锁优化_第1张图片

加锁过程
如果对象为无锁状态(锁标志位为01),则将锁对象的MarkWord拷贝一份到栈中(释放锁比较方便,CAS替换下就行),并使用CAS将指针指向。(这一步并没有加锁),如果CAS成功,则将栈中拷贝的这个MarkWord的锁标志位设置为00(轻量级锁状态)。如果CAS失败,则检查锁对象的MarkWord指针是否指向当前栈中的拷贝MarkWord。如果指向,则代表该锁已经被当前线程持有。啥也不干。如果不指向。则代表有多个线程在竞争该锁。则尝试自旋,自旋失败升级为重量级锁锁标志位变为10。

解锁过程
因为虚拟机线程栈帧中的 Displaced Mark Word 是最初的无锁状态时的数据结构,所以用它来替换对象头中的 Mark Word 就可以释放锁。如果锁已经膨胀为重量级,此时是不可以被替换的,所以替换失败,唤醒被挂起的线程。

参考
https://www.cnblogs.com/deveypf/p/11406932.html

你可能感兴趣的:(JavaSE)