让请求锁的线程执行忙循环(自旋)而不放弃处理器的执行时间,这项技术就是自旋锁。jdk1.4.2中引入,默认关闭。jdk6中默认开启。自旋次数默认10次,可以使用参数修改。jdk6中引入了自适应自旋,即自选时间不再固定,而是由前一次在同一锁上的自旋时间及锁的拥有者的状态来决定。如果在同一锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行,那么虚拟机认为这次自旋也很可能成功,进而允许自旋等待较长时间,比如100次忙循环。如果对某个锁,自旋很少成功获得锁,那么以后获取这个锁可能直接省略掉自旋过程。
锁消除是指虚拟机即时编译器在运行时,对一些代码要求同步,但是对被检测到不可能存在共享数据竞争的锁进行消除。主要判断依据来源于逃逸分析的数据支持。
如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作出现在循环体中,那即使没有线程竞争,频繁地进行互斥同步也会导致不必要的性能损耗。如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将把加锁同步范围扩展(粗化)到整个操作序列的外部,这样只需要加锁一次就可以了。
轻量级锁不是代替重量级锁的,设计初衷是在没有多线程竞争的前提下,减少重量级锁使用操作系统互斥量产生的性能消耗。轻量级锁提升程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”这一经验法则。
工作过程
代码进入同步块时,如果对象没有被锁定(锁标志位01),虚拟机在当前线程的栈帧中建立一个叫锁记录的空间,存储锁对象Mark Word的拷贝然后虚拟机使用CAS操作尝试把对象的Mark Word更新为指向锁记录的指针。如果更新成功,代表线程拥有了这个对象的锁,并且对象Mark Word锁标志位变为00,表示对象处于轻量级锁状态。如果更新失败,说明至少有一个线程与当前线程竞争对象锁。虚拟机首先检查对象Mark Word是否指向当前线程的栈帧,如果是,说明当前线程已经拥有了对象锁,那直接进入同步块继续执行就行,否则说明对象锁被其他线程抢占。如果出现两个以上线程争用同一个锁的情况,那么轻量级锁就膨胀为重量级锁,锁标志位变为10,Mark Word存储的是指向重量级锁(互斥量)的指针。释放锁时,如果对象的Mark Word指向线程的锁记录,那就用CAS把对象的Mark Word和线程中复制的Mark Word替换回来。如果成功替换,那同步过程结束;如果替换失败,说明其他线程尝试获取过该锁,在释放锁时,唤醒被挂起的线程。偏向锁会偏向于第一个获取它的线程,如果在接下来的执行过程中,没有其他线程获取这个锁,那么持有偏向锁的线程将永远不需要再进行同步。
当一个对象已经计算过一致性哈希码后,它就再也无法进入偏向锁状态;而当一个对象当前处于偏向锁状态,又收到需要计算一致性哈希码的请求时,它的偏向状态会被立即撤销,并且锁会膨胀为重量级锁。
图片来自
深入分析synchronzed和锁膨胀过程:https://juejin.im/post/6844903805121740814