synchronized锁升级原理

简介: synchronized锁升级原理

Java对象的内存结构


对象内存结构


在64位操作系统下,


MarkWord(下图_mark)占64位


KlassWord(下图_klass)占32位   64位系统的Klass Word不是32位,默认64位,开启指针压缩后为32(感谢评论老哥的指出)


64位系统的Klass Word不是32位,默认64位,开启指针压缩后为32


_lengh(只有数据对象才有,不考虑)


实例数据(下图instance data)看参数的类型,int就占32位(4byte)


补齐(padding)是JVM规定java对象内存必须是8byte的倍数,如果实例数据占2byte,那么(64bit的Markword+32bit的Klassword+实例数据32bit)=128bit=16byte是8byte的倍数,所以padding部分为0。


6.png


查看对象内存结构


JDK8


 <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>


public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
  }
}
class Dog {
  char age;
}


7.png


如上图所示,对象头中的MarkWord占8byte,KlassWord占4个byte,实例属性age是char类型占2个byte,那么此时加起来为14byte,为了满足是8的倍数,要补充2个byte。

下图是当Dog对象里的age变为int时打印的结果,请自行对比。


8.png


对象头


下图是引自《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) 周志明》中的一个图,下图是32操作系统下的对象头中的Mark Word(32位),Klass Word(32位),一共是64位。


64操作系统下,Mark Word的长度是64,在加Klass Word(32位),一共是96位,其实对象头长什么样其实不是本文的重点,本文的重点是验证锁升级的过程,所以我们只需要关注对象头中Mark Word的最后3位即可,如下图中的后3位。


9.png


锁升级的过程


微信截图_20230224221201.png


前提


由于大小端引起的问题,使得这里展示的高低位相反,如下图所示,所以我要关注的就是⑧位置的最后3位足矣


10.png


无锁状态


public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
  }
}


如下图所示,001表示的无锁状态并且不允许偏向 (其实默认是开启偏向的,只不过虚拟机后在运行后几秒才开启偏向锁)


11.png


使用下面的参数,如下图所示 ,会发现状态为101,表示无锁状态


-XX:BiasedLockingStartupDelay=0


12.png


由无锁状态---->偏向锁状态


单线程访问锁的时候,锁由无锁状态变为偏向锁状态。


// -XX:BiasedLockingStartupDelay=0
public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
    //上锁
    synchronized (dog){
      System.out.println(ClassLayout.parseInstance(dog).toPrintable());
    }
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
  }
}
class Dog {
  int age;
}


13.png


如上图所示,开始状态为101,为可偏向,无锁状态


上锁后状态是101,为可偏向,有锁状态


解锁后:状态为101,为可偏向,有锁状态


区别为:当线程给无锁状态的lock加锁时,会把线程ID存储到MarkWord中,即锁偏向于该ID的线程,偏向锁不会自动释放。


上面表格中2->3的过程。


偏向锁状态---->轻量级锁状态


多线程使用锁(不竞争,错开时间访问),锁由偏向锁状态变为轻量级锁状态


// -XX:BiasedLockingStartupDelay=0
public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println("初始状态:");
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
    new Thread(
            () -> {
              synchronized (dog) {
                System.out.println("hello world");
              }
            },
            "t1")
        .start();
    System.out.println("线程1释放锁后:");
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
    try {
      TimeUnit.SECONDS.sleep(3);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
    }
    new Thread(
            () -> {
              synchronized (dog) {
                System.out.println("线程2上锁:");
                System.out.println(ClassLayout.parseInstance(dog).toPrintable());
              }
              System.out.println("线程2释放锁:");
              System.out.println(ClassLayout.parseInstance(dog).toPrintable());
            },
            "t2")
        .start();
  }
}
class Dog {
  int age;
}


14.png


初始状态为101,为可偏向,并且为无锁状态

线程1释放锁后,状态为101,并且存储了线程ID,为偏向锁状态,偏向于线程1

线程2上锁,上锁后,状态为00,轻量级锁状态

线程2释放锁后,状态为001,此时为不可偏向的无锁状态。


重量级锁状态


// -XX:BiasedLockingStartupDelay=0
public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println("初始状态:");
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
    new Thread(
            () -> {
              synchronized (dog) {
                System.out.println("");
                try {
                  TimeUnit.SECONDS.sleep(3);
                } catch (Exception e) {
                  e.printStackTrace();
                } finally {
                }
              }
            },
            "t1")
        .start();
    new Thread(
            () -> {
              synchronized (dog) {
                System.out.println("线程2上锁");
                System.out.println(ClassLayout.parseInstance(dog).toPrintable());
                try {
                  TimeUnit.SECONDS.sleep(3);
                } catch (Exception e) {
                  e.printStackTrace();
                } finally {
                }
              }
              System.out.println("线程2释放锁");
              System.out.println(ClassLayout.parseInstance(dog).toPrintable());
            },
            "t2")
        .start();
  }
}
class Dog {
  int age;
}


15.png


如上图所示,锁初始状态为101,可偏向无锁状态

当线程1在使用锁,而线程2去上锁的时候,状态已经变为010,不可偏向重量级锁。


总结


单线程使用锁的时候为偏向锁。

多线程无竞争(错峰使用锁)的时候为轻量级锁。

有竞争的时候为重量级锁。

目录
相关文章
|
6月前
|
存储 缓存 安全
关于Synchronized锁升级,你该了解这些
关于Synchronized锁升级,你该了解这些
95 3
|
3月前
|
存储 Java C++
人人都会的synchronized锁升级,你真正从源码层面理解过吗?
本文结合Jvm源码层面分析synchronized锁升级原理,可以帮助读者从本质上理解锁升级过程,从锁如何存储,存储在哪的疑问出发,一步一步彻底剖析偏向锁,轻量级锁,自旋锁,重量级锁的原理和机制。
人人都会的synchronized锁升级,你真正从源码层面理解过吗?
|
5月前
|
Java 程序员
【面试官】知道synchronized锁升级吗
线程A获取了某个对象锁,但在线程代码的流程中仍需再次获取该对象锁,此时线程A可以继续执行不需要重新再获取该对象锁。既然获取锁的粒度是线程,意味着线程自己是可以获取自己的内部锁的,而如果获取锁的粒度是调用则每次经过同步代码块都需要重新获取锁。此时synchronized重量级锁就回归到了悲观锁的状态,其他获取不到锁的都会进入阻塞状态。来获得锁,CAS操作不需要获得锁、释放锁,减少了像synchronized重量级锁带来的。轻量级锁通过CAS自旋来获得锁,如果自旋10次失败,为了减少CPU的消耗则锁会膨胀为。
154 4
|
5月前
|
安全 Java 编译器
synchronized同步锁 : 原理到锁升级及历史演进的解析
synchronized同步锁 : 原理到锁升级及历史演进的解析
|
6月前
|
安全 算法 Java
Java多线程基础-15:Java 中 synchronized 的优化操作 -- 锁升级、锁消除、锁粗化
`synchronized`在Java并发编程中具有以下特性:开始时是乐观锁,竞争激烈时转为悲观锁;从轻量级锁升级至重量级锁;常使用自旋锁策略;是不公平且可重入的;不支持读写锁。
50 0
|
Java 编译器
Java多线程(4)---死锁和Synchronized加锁流程
Java多线程(4)---死锁和Synchronized加锁流程
73 0
|
Java 编译器 Linux
【多线程】锁策略、CAS、Synchronized
锁策略, cas 和 synchronized 优化过程
|
6月前
|
存储 安全 Java
Synchronized锁工作原理
Synchronized锁工作原理
|
6月前
|
Java 编译器 程序员
synchronized 原理(锁升级、锁消除和锁粗化)
synchronized 原理(锁升级、锁消除和锁粗化)
|
存储 Java
面试~Synchronized 与 锁升级
面试~Synchronized 与 锁升级
59 0