锁优化
锁优化
高效并发 是从jdk1.5到jdk1.6的一个重要改进,HotSpot开发团队在这个版本上花费了大量的精力去实现各种锁优化技术:适应性自旋(Adaptive Spinning)、锁消除(Lock Elimination)、锁粗化(Lock Coarsening)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking);
这些技术都是为了在线程之间更高效共享数据,已解决竞争问题,从而提高程序的执行效率;
自旋锁&自适应自旋
互斥同步 对性能最大的影响是阻塞,挂起和恢复线程操作都需要转入内核态完成;
虚拟机开发团队 注意到,在很多应用上,共享数据的锁定只会持续很短的一段时间,为了这段时间挂起和恢复线程并不值得;
如果物理机有一个以上的CPU,能让2个或以上的线程同时并行执行,可以让后面等待锁的线程“等一等”,但不放弃CPU资源,看持有锁的线程是否很快释放锁;
为了让线程等待,只需要让一个线程执行忙循环(自旋),这项技术就叫自旋锁;
自旋锁 在jdk1.4就引入,默认关闭,可以使用-XX:+UseSpinning开启;jdk1.6默认开启;
自旋等待不能代替阻塞,且 先不说对CPU数量的要求,自旋锁本身虽然避免了线程切换的开销,但要占用CPU资源,如果锁被占用时间很短,自旋等待效果很好;
反之,自旋的线程会白白浪费CPU资源,不会做任何有用的工作,反而带来性能的浪费;
自旋等待的时间必须有一定限度,如果自旋等待次数超过限定仍然没有成功获得锁,就应使用传统的方式挂起线程了;
自旋次数默认是10,用户可以使用-XX:+PreBlockSpin自定义;
jdk1.6引入了 自适应自旋锁;
自适应 意味着 自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者状态来决定;
如果在同一个锁对象上,上一个线程自旋等待刚刚获得过锁 且 正在运行,JVM认为这次自旋很有可能成功,进而它将允许自旋等待持续更长时间;
如果对于某个锁对象,自旋很少获得成功,以后获取锁将忽略掉自旋,避免浪费CPU资源;
有了自适应自旋,随着程序运行和性能监控信息不断完善,JVM对程序锁的状况预测越来越准确;
锁消除
锁消除 指 JVM即时编译器 在运行时,对一些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进行消除;
锁消除主要判断依据:逃逸分析的数据支持 ;
(如果在一段代码中,堆上所有数据都不会逃逸出去从而被其他线程访问,可以把他们当做栈上数据对待,认为是线程私有,无需同步加锁)
变量是否逃逸,JVM需要使用数据流分析来确定,
锁粗化
原则上,编写代码的时候,总是推荐将同步代码块的作用范围尽量小-只在共享数据的实际作用域进行同步;
大部分情况下,上面的原则是正确的;
但如果一系列的操作 对 同一个对象反复加锁/释放锁(甚至加锁出现在循环体中),即使没有线程竞争,频繁进行互斥同步操作也会造成不必要的性能损耗;
JVM检测到如果有一系列的操作都对同一个对象加锁,会把整个加锁的范围扩展(粗化)到整个操作序列的外部;
轻量级锁
轻量级锁 是jdk1.6 中加入的新型锁机制;
“轻量级” 是相对于使用系统互斥量的传统锁而言,因此传统锁机制被称为“重量级”锁;
轻量级锁 不是用来代替 重量级锁,而是在没有锁竞争的情况下,减少使用传统锁带来的性能损耗;
要理解轻量级锁,必须从Hotspot VM的对象头内存布局说起:
对象头包括:
Mark Word(存储对象自身的运行数据 [ hashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳 ])、
类型指针(指向 方法区对象类型数据的指针)、
[数组还会存储数组的长度]
对象头信息 是与 对象自身定义的数据无关的额外存储成本,考虑到VM的空间效率,Mark Word被设计为一个非固定的数据结构以便在极小的内存存储更多的信息;
加锁过程:
在进入同步代码块时,如果此对象的没有被锁定(锁状态标志01),VM将在当前线程的栈桢中建立一个lock Record的空间(用于存储同步对象的Mark Word的拷贝);
然后,VM使用CAS操作尝试将同步对象的Mark Word更新为指向Lock Record的指针,如果更新成功,当前线程将持有该同步对象的锁,同步对象的锁状态标志更新为00;
如果CAS操作失败,VM检查同步对象的MarkWord是否指向 当前线程栈桢;
如果 当前线程已持有锁,直接进入同步块执行;
如果 被其他线程占用,要膨胀为 重量级锁,同步对象的锁状态标志更新为10,后面等待的线程会进入阻塞;
解锁过程:
如果同步对象的Mark Word指向当前线程的栈桢,用CAS操作将同步对象的MarkWord和当前线程的LockRecord进行交换;
如果交换成功,同步过程完成;
如果交换失败,说明有其他线程尝试获取该同步对象,在释放锁的同时,唤醒被挂起的线程;
轻量级锁提升性能的依据:对于大部分锁,不存在锁竞争关系;
如果没有锁竞争,轻量级锁使用CAS操作避免使用互斥量的开销;
如果有锁竞争,除了互斥量的开销,还有CAS操作,所以有锁竞争的情况下,轻量级锁会比传统的锁更慢;
偏向锁
偏向锁 是 jdk1.6引入的一项锁优化;
偏向锁目的是 消除共享数据在无竞争情况下的同步原语,进一步提高程序的性能;
如果说 轻量级锁 是在无竞争的情况下使用CAS操作消除同步使用的互斥量,偏向锁 是在无竞争的情况下,消除整个同步(CAS也不做);
偏向锁的意思:偏向第一个获得它的线程,如果没有锁竞争存在,持有该锁的线程将不需要同步;
假设当前VM启用了偏向锁(-XX:+UseBiasedLocking),
当前同步对象第一次被线程获取的时候,VM会把同步对象的对象头标志位更新为01(偏向模式);
再使用CAS操作把线程ID 更新到 同步对象的MarkWord中,如果更新成功,该线程进入同步块不需要同步操作;
当另一个线程尝试获取锁时,偏向模式结束,同步对象的MarkWord更新为01(未锁定)或轻量级锁...;
偏向锁 可以提高 带有同步但无竞争的程序性能;
大多数情况下,锁是被竞争的,偏向模式有点多余,有时候禁用偏向锁可以提升性能(-XX:-UseBiasedLocking);