Java中锁的种类大致分为偏向锁,自旋锁,轻量级锁,重量级锁。
锁的使用方式为:先提供偏向锁,如果不满足的时候,升级为轻量级锁,再不满足,升级为重量级锁。自旋锁是一个过度的锁状态,不是一种实际的锁类型。
-
重量级锁
对象头:储存对象第hashCode、锁信息或分代年龄或GC标志,类型指针指向对象的类元数据,JVM通过这个指针确定改对象是哪个类的实例等信息。
实例变量:存放类的属性数据信息,包括父类的属性信息
填充数据:由于虚拟机要求对象其实地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为类字节对齐
当在对象上加锁时,数据是记录在对象头中。当执行synchronized同步方法或同步代码块时,会在对象头中记录锁标记,标记指向的是monitor对象(也称为管程或监视器锁)的其实地址。每个对象都存在着一个monitor与之关联,对象与其monitor之间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,当一个monitor被某个线程持有后,它便处于锁定状态。
在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的。
ObjectMonitor中有两个队列,_WaitSet和_EntryList,以及_Owner标记。其中_WaitSet是用于管理等待队列(wait)线程的,_EntryList是用于管理锁池阻塞线程的,_Owner标记用来记录当前执行线程。
当多线程并发访问同一个同步代码时,首先会进入_EntryList,当线程获取锁标记后,monitor中当_Owner记录此线程,并在 monitor中当计数器执行递增计算(+1),代表锁定,其他线程在 _EntryList中继续阻塞。若执行线程调用wait方法,择monitor中的计数器执行赋值为0计算,并讲_Owner标记复制为null,代表放弃锁,执行进程进入_WaitSet中阻塞。若执行进程调用notify/notifyAll方法,_WaitSet中的线程被唤醒,进入_EntryList中阻塞,等待获取锁标记。若执行线程的同步代码执行结束,同样会是否锁标记,monitor中_Owner标记赋值为null,且计数器自减计算 (-1)。
- 偏向锁
是一种编译解释锁。如果代码中不可能出现多线程兵法争抢同一个锁的时候,JVM编译代码,解释执行的时候,会自动放弃同步信息。消除synchronized的同步代码结果。使用锁标记的形式记录锁状态。再Monitor中有变量ACC_SYNCHRONIZED。当变量值使用的时候,代表偏向锁锁定。可以避免锁的争抢和锁池状态的维护,提高效率。 - 轻量级锁
过度锁。当偏向锁不满足,也就是有多线程并发访问,锁定同一个对象的时候,先提升为轻量级锁。也是使用标记ACC_SYNCHRONIZED标记记录的。ACC_UNSYNCHRONIZED标记记录未获取到锁信息的线程。就是只有两个线程争抢锁标记的时候,优先使用轻量级锁。
两个线程也可能出现重量级锁。 - 自旋锁
是一个过渡锁,是偏向锁和轻量级锁的过渡。
当获取锁的过程中,未获取到。为了提高效率,JVM自动执行若干次空循环,在次申请锁,而不是进入阻塞状态的情况。称为自旋锁。