一.自旋锁:
在JDK1.6之前,在多处理器上,两个线程并行执行访问同一个对象锁时,其中一个线程持有了对象锁之后,另一个线程不会被挂起,而是继续占用CPU资源进行一定次数的轮询等待获取锁,超过次数则被挂起,默认是10次,可以通过参数-XX PreBlockSpin更改.
在JDK1.6之后,自旋的时间不在固定,而是由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定,也就是在同一个锁对象上,自旋等待刚刚成功了并且锁的拥有者的线程正在运行,那么虚拟机就会认为此次自旋也很有可能成功,换句话说,如果前面锁对象的拥有者自旋都没有成功,那么现在也不会去进行自旋等待获取锁了.这是一种自适应自旋锁.
目的:减少挂起线程和恢复线程之间的用户态到内核态之间的转换所带来的性能影响.
操作参数:-XX:+UseSpinning,JDK1.6之后是默认开启
二.锁消除:
虚拟机即时编译器在运行时,对一些代码上要求同步,而通过逃逸分析数据支持检测到不可能存在共享数据竞争的锁进行消除.
举例:StringBuffer对象的多个append操作是有同步加锁,此对象只是在一个方法内部进行操作,对象的引用永远不会逃逸到方法之外,其他线程无法访问到.
三.锁粗化:
虚拟机检测到一系列连续操作都对同一个对象反复加锁和解锁,虚拟机会把加锁同步的范围扩展(粗化)到整个操作序列的外部.
举例:StringBuffer对象的多个append操作加锁就会扩展到第一个append前和最后一个append后,只进行一次加解锁.
目的:尽量减少同一对象的加锁和解锁操作.
四.轻量级锁:
实现的依据来源于HotSpot虚拟机对象头的两部分信息
(一部分是用于存储对象自身运行时数据[类的元数据信息,对象哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等],根据当前对象状态标志位来存储不同内容.例如:标志位01(未锁定状态)存储对象哈希码,分代年龄 / 标志位00(轻量级锁状态)存储指向锁记录的指针 / 标志位10(膨胀为重量级锁)存储指向重量级锁的指针 / 标志位11(GC标记)不存储信息 / 标志位01(可偏向状态)存储偏向线程ID,偏向时间戳,对象分代年龄)
(另一部分存储指向方法区对象类型数据的指针,如果是数组对象,还有额外部分用于存储数组长度)
加解锁过程:
加锁过程为在访问同步块时,如果此同步对象没有被锁定(锁标志位01),虚拟机会先在当前线程的栈帧中建立一个名为锁记录的空间,用于存储锁对象目前的Mrak word的拷贝,然后使用CAS操作尝试将对象的Mark word更新为指向锁记录的指针.情况如下:
1.成功则此线程拥有了对象锁并且将锁标志置为00(轻量锁状态).
2.失败则检查对象的Mark Word是否指向当前线程的栈帧,是的话则认为已经获得了锁直接进入同步快执行,否则将膨胀为重量级锁,锁标志位为10,Mark word存储为指向重量级锁的指针,线程进入阻塞状态被挂起.
解锁过程也是CAS操作,将对象的Mark word和线程中拷贝的Mark Word替换回来,成功则解锁完成,失败则说明有其他线程尝试获取过该锁,就需要释放锁并唤醒被挂起的线程.
目的:无竞争的情况下使用CAS操作去消除同步使用的互斥量.当然,一旦锁膨胀则额外多了CAS操作,影响性能.
五.偏向锁:
当锁对象第一次被线程获取时,对象头的标志位置为01(可偏向状态),同时使用CAS操作吧获取的这个锁的线程ID记录在此对象的Mark word 中,如果成功则持有偏向锁的线程在无竞争的情况下,以后进入这个锁相关的同步块不需要任何操作,一旦此对象锁有竞争则退出偏向锁转换为轻量级锁或无状态.
目的: 无竞争的情况下把整个同步都消除掉.
操作参数: -XX:+UseBiaedLocking