java多线程 偏向锁 轻量级锁 重量级锁
java的对象有三个存储区域,对象头,实例数据,对齐填充。
其中对象头中由两部分区域:
1:对象的类型指针,jvm通过这个指针可以找到该对象是哪个类的实例。但是访问对象的元数据信息(元数据是添加到
程序元素如方法、字段、类和包上的额外信息。对数据进行说明描述的数据)不一定经过数据本身可以通过句柄,或者
直接指针的方式访问。如果对象是java数组,对象头中还要有记录数组长度的数据。因为普通对象可以通过元数据信息
确定对象的大小,而根据数组的元数据无法确定数组对象的大小,也就无法为对象分配内存。
2:存储对象自身运行时的数据:哈希码、GC分代年龄、偏向时间戳、偏向线程ID、线程持有的锁、锁状态标志等。
对象头中的锁是我们今天的主要讨论范围。
Java对象头里的Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下4种数据:
偏向锁:
偏向锁加锁:
当我们的某个线程拥有共享资源并且获得锁的时候,实现hotspot的设计者会通过调用CAS在对象头的Mark Word
里存储锁偏向线程的id。这样该线程再次访问共享的临界资源时候,不需要再进行同步操作和CAS操作
偏向锁释放锁:
当一个线程拥有偏向锁之后,另一个线程来访问这个临界资源,此时会检测拥有偏向锁的进程是否存活。如果线程
不存活,则该锁变成无锁状态,然后升级为轻量级锁,偏向下一个申请锁进程。如果线程存活,则直接变成轻量级
锁。然后由持有锁的线程继续执行。
轻量级锁:
轻量级加锁:
当线程进入同步区之前,会在线程帧中开辟一块资源来记录存储锁的记录,并将对象头的Mark Word复制到锁记
录中然后线程试着用CAS将对象头中的Mark Word替换为指向锁记录的指针官方称为(Displaced Mark Word)。如
果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
轻量级释放锁:
轻量级解锁时,会使用原子的CAS操作来将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发
生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。
重量级锁:
当两个线程在竞争锁的时候,这时候的锁是轻量级的锁。一个线程获得锁,另一个线程自旋等待锁。但是如果自旋
次数太多或者两个线程以上争这个锁的时候就会升级为重量级锁。这样只有获得锁的线程才能运行,其他线程都阻塞。
防止cpu空转。
为了提高获得锁和释放锁的效率,锁一旦升级就不能在降级。这样设计锁的目的是更好的利用cpu。根据线程的多少
和竞争资源的程度来决定用哪种锁,能更好的利用cpu资源。
多线程锁机制部分参考博客【Java多线程 锁优化】锁的三种状态切换