synchronized关键字是jvm中悲观锁的一种实现方式,1.6之后做了一些优化,并不是简单的悲观锁了,涉及到偏向锁、轻量级锁、重量级锁的转化,使用的理解其实很简单,就是传入要锁的对象,锁定的代码块要想执行,一定要去获取这个对象锁才行。锁的使用一定有资源的共享和互斥的关系,最简单的例子:多线程去递增一个数,这个数是线程共享的,而增加的操作应该是互斥的。
每个对象都存储了自己的锁信息,总体来说对象锁存储在对象头里面。在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
Mark word 记录了对象和锁有关的信息,当某个对象被synchronized 关键字当成同步锁时,那么围绕这个锁的一系列操作都和 Mark word 有关系。Mark Word 可能变化为存储以下 5 中情况。
类元信息,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对对象的大小必须是8字节的整数倍。当对象其他数据部分没有对齐时,就需要通过对齐填充来补全。
大部分情况下,锁不仅仅不存在多线程竞争,而是总是由同一个线程多次获得,为了让线程获取锁的代价更低就引入了偏向锁的概念。怎么理解偏向锁呢?当一个线程访问加了同步锁的代码块时,会在对象头中存储当前线程的ID,后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接比较对象头里面是否存储了指向当前线程的偏向锁。如果相等表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了
a)如果相等,不需要再次获得锁,可直接执行同步代码块
b)如果不相等,说明当前锁偏向于其他线程,需要撤销偏向锁并升级到轻量级锁
偏向锁的撤销并不是把对象恢复到无锁可偏向状态(因为偏向锁并不存在锁释放的概念),而是在获取偏向锁的过程中,发现cas失败也就是存在线程竞争时,直接把被偏向的锁对象升级到被加了轻量级锁的状态。对原持有偏向锁的线程进行撤销时,原获得偏向锁的线程有两种情况:
原获得偏向锁的线程如果已经退出了临界区,也就是同步代码块执行完了,那么这个时候会把对象头设置成无锁状态并且争抢锁的线程可以基于CAS重新偏向但前线程如果原获得偏向锁的线程的同步代码块还没执行完,处于临界区之内,这个时候会把原获得偏向锁的线程升级为轻量级锁后继续执行同步代码块在我们的应用开发中,绝大部分情况下一定会存在2个以上的线程竞争,那么如果开启偏向锁,反而会提升获取锁的资源消耗。所以可以通过jvm参数UseBiasedLocking来设置开启或关闭偏向锁