JVM之锁优化 锁总结
- Java对于锁的优化:归根结底是权衡操作系统中用户态、核心态切换的资源损耗与CPU空转资源损耗
- 偏向锁适用于单线程无竞争的情况
- 轻量级锁适用于存在线程竞争,但是不激烈的情况
- 重量级锁适用于竞争激烈的情况(当CAS的损耗>操作系统用户态、核心态切换的损耗时,转变为重量级锁)
自旋锁
- 为什么引入自旋锁?
- 在实现互斥同步时,我们需要借助操作系统;在这个过程中,可能会存在用户态到和核心态之间的切换(这个切换十分消耗资源)
- 引入自旋锁:我们默认当前线程阻塞的时间很短,当前线程可以很快获取锁;而不要依赖操作系统,让当前线程处于一个循环的状态(自旋)
- 目的:通过自旋来降低操作系统状态转换、线程切换的开销
- 自旋锁存在问题:
- 假设当前线程无法短时间获取处理器执行权,则线程会处于一个持久的空转状态(自旋);这个空转所造成的开销可能导致大于线程切换的开销
自适应锁
- 自适应锁:
- 解决自旋时间过长、过度消耗资源的问题
- 通过上一次自旋的时长来决定下一次自旋的时长(如果上一次自旋过长,则认为我将难以获取锁;延长我自旋的时间,甚至放弃自旋;若上一次自旋时间很短,则认为我也很容易获取锁,降低自旋的时间)
- 锁消除:
- 指JVM在对代码进行编译时,对一些代码要求同步,但是经JVM检测不可能发生数据竞争,进而消除锁
- 如代码块:
- StringBuffer类的每一个方法都需要synchronized修饰,但是stringBuffer永远无法逃逸出sysTest方法作用域,故而不会发生任何的数据竞争
- 锁粗化:
- 指需要对一个对象进行重复加锁、释放锁;这是性能损耗也很高,锁粗化为第一个操作开始前以及最后一个操作执行后;
- 如代码块:
- 对象MarkWord布局图:
- 偏向锁
- 只比较当前线程的ID与被锁对象的对象头MarkWord中的线程ID;不进行CAS操作
- 当锁对象第一次被线程获取时,ThreadID写入锁对象的MarkWord,修改标志位为01,偏向模式为1
- 当ThreadID与对象头ThreadID不一致时(发生竞争时);修改标志位为01或00、偏向模式为0(撤销偏向锁,升级为轻量级锁)
- 轻量级锁
- 通过CAS操作,减少重量级锁的使用,进而减少重量级锁所带来的性能损耗
- 1.当锁的标置位为01时,当前线程栈会新建锁记录(Lock Record)用以记录被锁对象的MarkWord
- 2.而后会通过CAS将对象头的MarkWord更新为指向线程栈的Lock Record的指针;
- 3.如果更新成功,也就代表了当前线程拥有了锁,同时修改标志位为00
- 4.如果更新失败,则说明至少存在一个线程与当前线程竞争
- 4.1 再次比较MarkWord指针是否指向当前线程栈帧;若指向当前栈帧,则执行同步代码块
- 5.重复执行若干次轻量级锁CAS操作后,若对象头的MarkWord指针还是未指向当前线程栈,则锁升级为重量级锁(轻量级锁->重量级锁)
- 重量级锁
- 在轻量级锁进行若干次CAS操作后,被锁对象的MarkWord指针依然没有指向当前线程栈,则升级为重量级锁;
- 标志位修改为10,此时被锁对象的MarkWord指向重量级锁(互斥量,依赖于操作系统);而后所有的等待线程需进入block状态
- 锁升级流程图(CV大法):