synchoronized在Jdk1.6之前一直被称为重量锁,性能较差,很少被推荐使用。synchoronized在Jdk1.6中进行了大量优化,性能与JUC中的各种锁无差,也被逐渐的被大众所接受。
synchoronized将锁状态记录在了对象头的MarkWord中,包含4种锁状态:无锁、偏向锁、轻量锁和重量锁,锁状态只能升级不能降级,即 无锁-->偏向锁-->轻量锁-->重量锁,目的是提高获取锁和释放锁的效率,对象几种状态的获取和释放如下:
- 偏向锁
大多数情况下,锁不存在多线程竞争,而总是由同一线程获得,为了让线程获得锁的代价更低(消除了同步)而引入了偏向锁。当对象第一次被线程获取的时候,会把MarkWord中的偏向标识位设为01(偏向锁状态),同时使用CAS操作将该线程的ID记录在MarkWord中,如果CAS操作成功,则该线程每次进入到这个锁相关的同步块时,虚拟机都可以不进行任何同步操作。
当有另外的线程获取这个锁时,偏向模式就结束了,根据锁对象目前是否处于锁定的状态,会恢复到无锁状态(01)或者变为轻量锁状态(00),借用《Java并发编程艺术》的一张图,如下所示:
- 轻量锁
线程在执行同步块之前,JVM会先在当前线程栈帧中创建用于存储锁记录的空间,并将对象头中的MarkWord复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS操作将对象头中的MarkWord替换为指向锁记录的指针,如果成功,当前线程获得锁,如果失败,表示有其他线程竞争锁,当前线程开始自旋,如果自旋完成一定次数(可能是50次、100次,可以通过启动参数配置自旋次数)当前线程还没有获取到锁,那么锁会开始膨胀,进化为重量锁。
轻量锁解决时,使用CAS操作将Displace Mark Word 替换会对象头,如果成功,则表示没有竞争,如果失败(已经有线程将锁的状态修改为重量锁),则释放锁并唤醒等待线程,借用《Java并发编程艺术》的一张图,如下所示:
- 重量锁
由上面的轻量锁升级而来,一旦升级到重量锁,那么其他线程获取锁,都会被阻塞,等待释放锁。当持有锁的线程释放锁并唤醒等待线程时,等待的线程就会开始新一轮的竞争。
理解:
- 当单个线程进入同步块,使用偏向锁,成本低,不需要使用CAS操作
- 当两个线程依次进入同步块,使用轻量锁,单线程自旋也不会造成太多的CPU损耗
- 当两个或多个线程进入同步快,使用重量锁,让获取锁的线程之外的线程进行阻塞等待唤醒,效率更高。
PS:使用openjdk的JOL(Java Object Layout)工具可以观察各种线程竞争锁时,Java对象头的状态。
参考书籍及网址:
- 《Java并发编程艺术》
PS:研究基于MAC+Idea+JDK1.8 64位
Keep Calm and Carry on!