在JAVA中锁一共有四种状态:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态(按从低到高顺序,锁着竞争情况逐渐升级)
JAVA中锁只能升级却不能降级,目的是为了提高获得锁和释放锁的效率。
在HopSpot虚拟机中,对象在内存存储中分为3部分:对象头(Header)、实例数据(Instance Data) 和 对齐填充(Padding)。 想了解Java对象结构的详细信息请看:java对象结构
java对象头包含3部分信息,如下:
锁的状态保存在对象头的Mark Word中,以32位JDK为例:
在无多线程竞争锁的情况下,为了让同一线程获得锁的代价更低而引入了偏向锁。
CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码),它会先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。
线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录(Lock record)中,官方成为Displaced Mark Word。然后尝试使用CAS算法将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
轻量级锁解锁时,会使用原子的CAS操作将Displaced Mark Word替换回对象头,如果成功,则表示没有竞争发生。如果失败,表示当前存在竞争,锁就会膨胀成重量级锁。
因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。其他线程试图获取锁时,会被阻塞住,当持有锁的线程释放之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁竞争。
在多线程并发编程中synchronized一直是元老级角色,很多人称呼它为“重量级锁”。synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的,但是监视器锁本质又是依赖于底层操作系统的Mutex Lock来实现的,而操作系统实现线程之间的切换就需要从用户状态切换到核心状态,这个成本很高,状态之间转换需要相对比较长的时间,这就是synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock来实现的锁,我们称之为“重量级锁”。
在JDK1.6后,synchronized得到了种种优化后,在某些情况下已经没那么重了。