这里的锁优化主要是指 JVM 对 synchronized 的优化。bash
互斥同步进入阻塞状态的开销都很大,应该尽可能避免。在许多应用中,共享数据的锁定状态只会持续很短的一段时间。自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环(自旋)一段时间,若是在这段时间内能得到锁,就能够避免进入阻塞状态。app
自旋锁虽然能避免进入阻塞状态从而减小开销,可是它须要进行忙循环操做占用 CPU 时间,它只适用于共享数据的锁定状态很短的场景。布局
在 JDK 1.6 中引入了自适应的自旋锁。自适应意味着自旋的次数再也不固定了,而是由前一次在同一个锁上的自旋次数及锁的拥有者的状态来决定。性能
锁消除是指对于被检测出不可能存在竞争的共享数据的锁进行消除。优化
锁消除主要是经过逃逸分析来支持,若是堆上的共享数据不可能逃逸出去被其它线程访问到,那么就能够把它们当成私有数据对待,也就能够将它们的锁进行消除。ui
对于一些看起来没有加锁的代码,其实隐式的加了不少锁。例以下面的字符串拼接代码就隐式加了锁:spa
public static String concatString(String s1, String s2, String s3) {
return s1 + s2 + s3;
}复制代码
String 是一个不可变的类,编译器会对 String 的拼接自动优化。在 JDK 1.5 以前,会转化为 StringBuffer 对象的连续 append() 操做:线程
public static String concatString(String s1, String s2, String s3) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString();
}复制代码
每一个 append() 方法中都有一个同步块。虚拟机观察变量 sb,很快就会发现它的动态做用域被限制在 concatString() 方法内部。也就是说,sb 的全部引用永远不会逃逸到 concatString() 方法以外,其余线程没法访问到它,所以能够进行消除。指针
若是一系列的连续操做都对同一个对象反复加锁和解锁,频繁的加锁操做就会致使性能损耗。code
上一节的示例代码中连续的 append() 方法就属于这类状况。若是虚拟机探测到由这样的一串零碎的操做都对同一个对象加锁,将会把加锁的范围扩展(粗化)到整个操做序列的外部。对于上一节的示例代码就是扩展到第一个 append() 操做以前直至最后一个 append() 操做以后,这样只须要加锁一次就能够了。
JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:无锁状态(unlocked)、偏向锁状态(biasble)、轻量级锁状态(lightweight locked)和重量级锁状态(inflated)。
重量级锁也就是一般说synchronized的对象锁。
如下是 HotSpot 虚拟机对象头的内存布局,这些数据被称为 Mark Word。其中 tag bits 对应了五个状态,这些状态在右侧的 state 表格中给出。除了 marked for gc 状态,其它四个状态已经在前面介绍过了。
下图左侧是一个线程的虚拟机栈,其中有一部分称为 Lock Record 的区域,这是在轻量级锁运行过程建立的,用于存放锁对象的 Mark Word。而右侧就是一个锁对象,包含了 Mark Word 和其它信息。
轻量级锁是相对于传统的重量级锁而言,它使用 CAS 操做来避免重量级锁使用互斥量的开销。对于绝大部分的锁,在整个同步周期内都是不存在竞争的,所以也就不须要都使用互斥量进行同步,能够先采用 CAS 操做进行同步,若是 CAS 失败了再改用互斥量进行同步。
当尝试获取一个锁对象时,若是锁对象标记为 0 01,说明锁对象的锁未锁定(unlocked)状态。此时虚拟机在当前线程的虚拟机栈中建立 Lock Record,而后使用 CAS 操做将对象的 Mark Word 更新为 Lock Record 指针。若是 CAS 操做成功了,那么线程就获取了该对象上的锁,而且对象的 Mark Word 的锁标记变为 00,表示该对象处于轻量级锁状态。
若是 CAS 操做失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的虚拟机栈,若是是的话说明当前线程已经拥有了这个锁对象,那就能够直接进入同步块继续执行,不然说明这个锁对象已经被其余线程线程抢占了。若是有两条以上的线程争用同一个锁,那轻量级锁就再也不有效,要膨胀为重量级锁。
偏向锁的思想是偏向于第一个获取锁对象的线程,这个线程在以后获取该锁就再也不须要进行同步操做,甚至连 CAS 操做也再也不须要。
当锁对象第一次被线程得到的时候,进入偏向状态,标记为 1 01。同时使用 CAS 操做将线程 ID 记录到 Mark Word 中,若是 CAS 操做成功,这个线程之后每次进入这个锁相关的同步块就不须要再进行任何同步操做。
当有另一个线程去尝试获取这个锁对象时,偏向状态就宣告结束,此时撤销偏向(Revoke Bias)后恢复到未锁定状态或者轻量级锁状态。