Java中锁的深入理解

目录

对象头的理解

Monitor(锁)

锁类型

偏向锁

偏向锁的优化机制

轻量级锁

重量级锁


对象头的理解

在32位Java虚拟机中普通对象的对象头是占用8个字节,其中4个字节为Mark Word。用来存储对象的哈希值,对象创建后在JVM中的生命(经历GC回收后存活次数)等信息。另外四个字节为Klass Word用来存储对象类型,是String还是Student又或是Teacher。

Mark Word的存储结构为

Java中锁的深入理解_第1张图片

Monitor(锁)

Monitor锁结构如下

Java中锁的深入理解_第2张图片

这三个区域分别代表

  1. WaitSet:线程通过wait()方法进入的阻塞状态,从Owner进入WaitSet集合,通过notify()方法唤醒后进入EntryList。
  2. EntryList:阻塞线程集合,需要通过非公平竞争去获取锁
  3. Owner:存储当前获取锁的线程。

当一个对象被加入重量级锁时,Mark Word会存储Monitor的地址。

Java中锁的深入理解_第3张图片

锁类型

均针对于synchronized来进行辨析

偏向锁

偏向锁CPU损耗是最小的一种,在这种锁状态下,系统会认为这个锁通常只会由一个线程独占。默认情况下是开启的,对于本就是需要多线程争抢的对象来说,可以选择禁用偏向锁。

轻量级锁每次都会进行CAS操作,这也会消耗CPU资源,因此,偏向锁采用的是对象中的Mark Word存储的是线程ID。每次加锁时,先判断对象中的Mark Word是否是本线程ID(轻量级锁和偏向锁的锁对象Mark Word存储的信息不同,轻量级锁存储的是对象的hash值,偏向锁存储的是ThreadID)。

当对象被加入偏向锁后,记录的ThreadID并不会因为解锁而消失,而是继续存储在对象的Mark Word中。如果其他线程获取该对象加锁,会撤销ThreadID,变成轻量级锁

当一个可偏向对象调用hashCode方法后,会关闭偏向锁,用来对象的Mark Word用来存储该对象的hash值。

偏向锁的优化机制

批量重偏向:当可偏向对象因为被多个线程访问导致的撤销偏向锁次数过多时(默认值为20),后续的可偏向对象就不会再次进行撤销偏向锁了,因为撤销偏向锁也是需要CPU资源的。而是进行重偏向,JVM会认为初始偏向时,偏向错了,将后续的可偏向对象中的Mark Word修改为现在正在执行的ThreadID,已经被撤销偏向锁的对象则无法恢复。

批量撤销:当偏向锁被撤销次数过多时(默认为40次),JVM会认为,就不应该开启偏向锁,此后创建的每一个对象都是不可偏向的。

轻量级锁

在这种锁状态下,通常应用是一个对象虽然会被多个线程访问,但是多个线程的访问是错开时间的,并不存在竞争关系。

static final Object obj = new Object(); 
public static void method1(){
	synchronized(obj){
        method2();
    }
}

public static void method2(){
	synchronized(obj){
    }
}

首先这是一个线程执行,那么在执行method1时,栈帧中会创建一个Lock Record(锁记录)。里面存储对象的引用地址以及存储锁对象的Mark Word。

如下图所示

Java中锁的深入理解_第4张图片

接着进行下一步,Object reference指向锁对象,并尝试通过cas交换锁对象的Mark Word。

如下图所示

Java中锁的深入理解_第5张图片

如果cas成功,那么对象的对象头Mark Word存储的是锁记录的地址以及其状态,表示对该对象进行加锁

Java中锁的深入理解_第6张图片

如果cas失败,则要分情况看待:

  • 一种是,该对象已经被其他线程上锁,这时说明该对象存在竞争情况,进入锁膨胀状态
  • 一种是,该对象已经被该线程上锁,这属于锁重入,再加入一条Lock Record作为重入计数。

对于第二种情况进行分析。在进行method1时,又进行了method2,此时,会cas失败。但是并不影响。

Java中锁的深入理解_第7张图片

进行解锁时,取出的锁记录为null时,说明存在重入,此时重入计数-1。

如果取出的锁记录不为null,说明该线程已经不对该对象上锁了,进行真正的解锁操作,通过cas将对象的原有Mark Word恢复给该对象。如果成功的话说明解锁成功,如果cas失败说明进行了锁膨胀升级到了重量级锁,此时去执行重量级锁解锁流程。

重量级锁

最耗费CPU资源的加锁方式。

当线程0已经为对象加上了轻量级锁后,线程1再次对该对象加入轻量级锁,此时CAS失败,那么线程1会为该对象申请Monitor锁。然后自己进入Monitor锁中的EntryList

Java中锁的深入理解_第8张图片

申请Monitor锁后,会将其地址存储在该对象的Mark Word中。

Java中锁的深入理解_第9张图片

当线程0进行解锁操作时,会发现CAS解锁失败,此时会进入重量级解锁流程,即通过Monitor地址,找到Monitor锁后,将Owner设置为null,唤醒EntryList中的阻塞线程。


自旋优化:当线程加锁时发现锁被占用会进入阻塞状态,但是这样会大量进行线程上下文切换,比较占用CPU,因此可以使用自旋优化,所谓自旋优化,是线程循环获取锁,失败后还是要去获取锁,如果在规定次数内还没有获取到锁,那么进入阻塞状态,因为这是一个循环过程,也是要占用CPU资源的。注意:单核CPU没有自旋的必要。

你可能感兴趣的:(java)