likes
comments
collection
share

JVM 内置锁 synchronized 的几种状态概述

作者站长头像
站长
· 阅读数 6

synchronized 使用

通常我们说的 java 内置锁默认都是指的 JVM 给我们提供的 synchronized 关键字实现的 JVM 内置锁。 举个例子:

public class SynchronizedTest1 {

    public static void main(String[] args) throws InterruptedException {
        Object lockObj = new Object();
        // 使用方式1: 代码块
        synchronized (lockObj) {
            System.out.println(1);
        }
    }

    // 使用方式2: 非静态方法
    public synchronized void test1() {

    }

    // 使用方式3: 静态方法
    public static synchronized void test2() {

    }
}

代码块方式使用

我们可以通过 idea 的jclasslib Bytecode Viewer插件进行查看字节码指令信息,如下图所示: JVM 内置锁 synchronized 的几种状态概述 我们看到 synchronized底层是使用  monitor 机制来实现锁的获取、释放。会在代码快前后增加 monitorentermonitorexit 指令。

方法关键字方式使用

同样,如果是在方法上增加 synchronized关键字(由于jclasslib Bytecode Viewer查看方法信息不是很方便,下面我就通过 javap -verbose指令来进行演示),会在方法的 flags 上增加 ACC_SYNCHRONIZED关键字,原方法代码如下:

public synchronized void test() {
}

jclasslib Bytecode Viewer插件:

JVM 内置锁 synchronized 的几种状态概述

Access_Flag 访问标志 访问标识信息包括该Class文件时类和接口是否被定义成了public,是否是 abstract, 如果是类,是否被申明为成final。通过下面的源代码。

0x 00 21: 表示是0x0020 和0x0001的并集, 表示 ACC_PUBLIC 与 ACC_SYNCHRONIZED

访问标识符的具体指值和定义可以看看这个类, com.sun.org.apache.bcel.internal.classfile.AccessFlags 或者在这里查询 download.java.net/java/early_…

错误的使用方式

  1. 代码块方式使用锁的对象是 null

**锁定对象不能是 null , 如果是 null 程序运行的时候会提示 ******NullPointerException****空指针异常。

Object lock = null;
synchronized(lock) {
    System.out.println(100);
}

// 结果: 
// Exception in thread "main" java.lang.NullPointerException
//	at cn.xyz.juc.synchronized1.status.CleanLockTest.main(CleanLockTest.java:16)
  1. synchronized 修饰非静态方法,但调用带锁的关键字的对象不是同一个。
public class SynchronizedInvalidTest {

    static int i = 0;

    public static void main(String[] args) throws InterruptedException {
        // 锁异常1:共享对象为null
        Object lockObj;
        synchronized (lockObj) {
            System.out.println(1);
        }

        //锁异常2: 共享对象不同
        SynchronizedInvalidTest test1 = new SynchronizedInvalidTest();
        SynchronizedInvalidTest test2 = new SynchronizedInvalidTest();

        Thread thread1 = new Thread(() -> {
            for (int j = 0; j < 100; j++) {
                test1.add();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int j = 0; j < 100; j++) {
                test2.add();
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread1.join();
        System.out.println("i=" + i);
    }

    public synchronized void add() {
        i++;
    }
}

synchronized 原理

synchronized 信息如何记录?

前置内容 【JVM对象创建过程】 www.bilibili.com/video/BV1WK…

JVM 初始化零值分配后,虚拟机要进行必要的设置,例如设置这个对象是哪个实例,如何能够得到类的元数据信息、对象的哈希码、对象的 GC 分代年龄、锁状态等信息,这些信息都存放在对象头(Object Header)中。 JVM 内置锁 synchronized 的几种状态概述 在 HotSpot 虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。 HotSpot虚拟机对象的对象头部分包括两类信息。第一类是用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。对象头的另外一部分是类型指针,即对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定该对象是哪个类的实例。 JVM 内置锁 synchronized 的几种状态概述 对象头在 hotspot 虚拟机中的源码 markOop.cpp

// Bit-format of an object header (most significant first, big endian layout below):
//
//  32 bits:
//  --------
//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
//             size:32 ------------------------------------------>| (CMS free block)
//             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
//  64 bits:
//  --------
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
//  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
//  size:64 ----------------------------------------------------->| (CMS free block)
//
//  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
//  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
//  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
//  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

理论:Java 管程

Java 虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是采用管程(Monitor, 更常见的是直接称为 “锁”)来实现的。

Java 采用的是管程技术,synchronized 关键字及 wait()、notify()、notifyAll() 这三个方法都是管程的组成部分。而管程和信号量是等价的,所谓等价指的是用管程能够实现信号量,也能用信号量实现管程。但是管程利用OOP 的封装特性解决了信号量在工程实践上的复杂性问题,因此java采用管理机制。

MESA 模型

在管程的发展史上,先后出现过三种不同的管程模型,分别是:Hasen 模型、Hoare 模型和 MESA 模型。其中,现在广泛应用的是 MESA 模型,并且 Java 管程的实现参考的也是 MESA 模型。所以今天我们重点介绍一下 MESA 模型。

在并发编程领域,有两大核心问题:一个是互斥,即同一时刻只允许一个线程访问共享资源;另一个是同步,即线程之间如何通信、协作。这两大问题,管程都是能够解决的。

我们先来看看管程是如何解决互斥问题的。

管程解决互斥问题的思路很简单,就是将共享变量及其对共享变量的操作统一封装起来。假如我们要实现一个线程安全的阻塞队列,一个最直观的想法就是:将线程不安全的队列封装起来,对外提供线程安全的操作方法,例如入队操作和出队操作。

利用管程,可以快速实现这个直观的想法。在下图中,管程 X 将共享变量 queue 这个线程不安全的队列和相关的操作入队操作 enq()、出队操作 deq() 都封装起来了;线程 A 和线程 B 如果想访问共享变量 queue,只能通过调用管程提供的 enq()、deq() 方法来实现;enq()、deq() 保证互斥性,只允许一个线程进入管程。

JVM 内置锁 synchronized 的几种状态概述

管程模型的代码化语义那管程如何解决线程间的同步问题呢?

在管程模型里,共享变量和对共享变量的操作是被封装起来的,图中最外层的框就代表封装的意思。框的上面只有一个入口,并且在入口旁边还有一个入口等待队列。

JVM 内置锁 synchronized 的几种状态概述

那条件变量和条件变量等待队列的作用是什么呢? 其实就是解决线程同步问题。你可以结合上面提到的阻塞队列的例子加深一下理解(阻塞队列的例子,是用管程来实现线程安全的阻塞队列,这个阻塞队列和管程内部的等待队列没有关系,本文中一定要注意阻塞队列和等待队列是不同的)。

假设有个线程 T1 执行阻塞队列的出队操作,执行出队操作,需要注意有个前提条件,就是阻塞队列不能是空的(空队列只能出 Null 值,是不允许的),阻塞队列不空这个前提条件对应的就是管程里的条件变量。 如果线程 T1 进入管程后恰好发现阻塞队列是空的,那怎么办呢?等待啊,去哪里等呢?就去条件变量对应的等待队列里面等。此时线程 T1 就去“队列不空”这个条件变量的等待队列中等待。

这个过程类似于大夫发现你要去验个血,于是给你开了个验血的单子,你呢就去验血的队伍里排队。线程 T1 进入条件变量的等待队列后,是允许其他线程进入管程的。这和你去验血的时候,医生可以给其他患者诊治,道理都是一样的。

再假设之后另外一个线程 T2 执行阻塞队列的入队操作,入队操作执行成功之后,“阻塞队列不空”这个条件对于线程 T1 来说已经满足了,此时线程 T2 要通知 T1,告诉它需要的条件已经满足了。当线程 T1 得到通知后,会从等待队列里面出来,但是出来之后不是马上执行,而是重新进入到入口等待队列里面。

这个过程类似你验血完,回来找大夫,需要重新分诊。

JVM 源码 ObjectMonitor

ObjectMonitor 在 jvm 中的定义信息如下:

  // initialize the monitor, exception the semaphore, all other fields
  // are simple integers or pointers
  ObjectMonitor() {
    _header       = NULL;      // 对象头
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;         // 锁重入次数   
    _object       = NULL;      // 存储锁对象
    _owner        = NULL;      // 标识拥有该 monitor 的线程(当前获取锁的线程) 
    _WaitSet      = NULL;      // 等待线程(调用 waite) 组成的双向循环链表 _WaitSet 是第一个节点
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;      
    _succ         = NULL ;
    _cxq          = NULL ;     // 多线程竞争锁会先存储到这个单向链表中(FIFO)结构
    FreeNext      = NULL ;     // 存放在进入或者重新进入时被阻塞(Blocked)的线程(也就是竞争失败的线程)
    _EntryList    = NULL ;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

synchronized JVM 源码中使用的是操作系统什么 API ? hotspot 虚拟机中 linux x86 下面会调用汇编指令 cmpxchgl ,在 hotspot/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp 函数 Atomic::cmpxchg

synchroinzed 4种锁概述

内置锁分为 4 个状态,分别是:无锁,偏向锁,轻量级锁,重量级锁。在锁竞争的过程中会进行一个正向的锁升级过程。

锁升级过程

JVM 内置锁 synchronized 的几种状态概述

说明,锁升级状态是不可逆转的。

锁的状态转化

偏向锁、轻量级锁的状态转化及对象 Mark Word 的关系转换入下图所示: JVM 内置锁 synchronized 的几种状态概述

打印对象信息

对象的结构我们可以通过 org.openjdk.jol:jol-core 打印内存的布局信息 maven 依赖代码:

<!-- 查看Java 对象布局、大小工具 -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.16</version>
</dependency>

无锁

我们创建一个对象的时候,JVM 就会给我们创建的对象分配一个管程对象,我们一起来看看无锁状态下的对象信息。

测试代码:


/**
 * 无锁状态
 */
public class NoLockTest {

    public static void main(String[] args) {
        Object object = new Object();
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }
}

输出结果

java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

字段解释:

  • OFFSET : 地址偏移量,单位字节;
  • SIZE: 占用内存大小,单位字节;
  • TYPE DESCRIPTION: 类型描述,其中 object header 为对象头;
  • VALUE: 对应内存中当前存储的值(二进制表示)

JVM 优化:指针压缩

打印的结果我们可以看到,对象总大小位 16 字节,前 12 字节为对象头(我本地 jdk 1.8 默认开启指针压缩),后面 4 字节为对齐填充。

可以通过一下参数进行关闭:

-XX:-UseCompressedOops

我再执行一次,对象总大小 16 bytes, 前 8 bytes 是 mark word , 后 8 bytes 表示 kclass point

java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
8   8        (object header: class)    0x00000001238efc00
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

对象大小 16个字节

匿名偏向

当 JVM 启用了偏向锁模式(JDK 6默认开启),创建新的 Mark Word 的 Thread Id 为 0, 说明此时处于可偏向但是并未偏向任何线程,也叫做匿名偏向状态(anonymously biased)


import org.openjdk.jol.info.ClassLayout;

/**
 * 延迟偏向
 * 
 * 1.无锁状态
 * 2.匿名偏向
 */
public class BiasedLockDelayTest {

    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        //1.无锁状态
        System.out.println(ClassLayout.parseInstance(obj1).toPrintable());
        System.out.println();
        Thread.sleep(5000);
        //2.匿名偏向
        Object obj2 = new Object();
        System.out.println(ClassLayout.parseInstance(obj2).toPrintable());
    }
}

输出结果:

JVM 内置锁 synchronized 的几种状态概述

偏向锁

锁优化: 偏向锁延迟偏向

**偏向锁模式存在偏向锁延迟机制: **Hostpost 虚拟机再启动后有一个几秒(默认 4 秒)的延迟才对新对象进行开启偏向锁模式。 JVM 启动时会进行一系列的对象创建过程。在这个过程中大量 synchronized 关键字对对象加锁,这些锁多数都不是偏向锁。为了减少初始化时间, jvm 默认延迟加载偏向锁。

相关 JVM 参数:

//关闭延迟开启偏向锁 
‐XX:BiasedLockingStartupDelay=0 

//禁止偏向锁
‐XX:‐UseBiasedLocking

//启用偏向锁
‐XX:+UseBiasedLocking

测试代码(注意,这里需要注意的是需要创建两个对象,因为对象头信息的初始化是在 new关键字执行的时候初始化,笔者之前就遇到了这样的问题,导致实验失败):


import org.openjdk.jol.info.ClassLayout;

/**
 * 延迟偏向
 * 
 * 1.无锁状态
 * 2.匿名偏向
 */
public class BiasedLockDelayTest {

    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        //1.无锁状态
        System.out.println(ClassLayout.parseInstance(obj1).toPrintable());
        System.out.println();
        Thread.sleep(5000);
        //2.匿名偏向
        Object obj2 = new Object();
        System.out.println(ClassLayout.parseInstance(obj2).toPrintable());
    }
}

结果打印如下,延迟 5 秒后新创建的都默认开启匿名偏向, threadid = 0 。 JVM 内置锁 synchronized 的几种状态概述

偏向锁偏向状态跟踪

下面代码主要演示了对象从无锁,到偏向锁的过程。

触发场景:创建子线程去获取锁

代码如下:

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;

/**
 * 触发场景:创建子线程去获取锁
 * <p>
 * 偏向锁状态
 * <p>
 * 测试例子1: 调用 hashcode, 升级为重量级锁
 * 测试例子2: 调用 notify, 偏向锁升级为轻量级锁
 * 测试例子3: 调用 wait, 偏向锁升级为重量级锁
 */
@Slf4j
public class BiasedLockStateTest {

    public static void main(String[] args) throws InterruptedException {
        log.debug(ClassLayout.parseInstance(new Object()).toPrintable());
        Thread.sleep(4000);
        Object obj1 = new Object();
        log.debug(ClassLayout.parseInstance(obj1).toPrintable());
        new Thread(new Runnable() {
            @Override
            public void run() {
                log.info("threadId:" + ClassLayout.parseInstance(obj1));
                log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
                synchronized (obj1) {
                    log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
                    //测试例子1: 调用 hashcode, 升级为重量级锁
                    //obj1.hashCode();
                    //log.debug("invoke hashCode method ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());

                    //测试例子2: 调用 notify, 偏向锁升级为轻量级锁
                    //obj1.notify();
                    //log.debug("invoke notify method ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());

                    //测试例子3: 调用 wait, 偏向锁升级为重量级锁
//                    try {
//                        obj1.wait(3);
//                    } catch (InterruptedException e) {
//                        throw new RuntimeException(e);
//                    }
//                    log.debug("invoke wait method ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
                }
                log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
            }
        }, "thread0").start();

        Thread.sleep(5000);
        log.debug(ClassLayout.parseInstance(obj1).toPrintable());
    }
}

打印结果如下: JVM 内置锁 synchronized 的几种状态概述

偏向锁状态调用 hashcode 方法

调用锁对兑现的 obj.hashCode() 或者 System.identityHashCode(obj) 方法时候会导致该对象的偏向锁被撤销。因为一个对象,其 hashcode 只会被生成一次并且保存,偏向锁是没有地方存储  hashcode 的

  • 轻量级锁会在锁记录中记录 hashCode
  • 重量级锁会在 Monitor 中记录 hashCode

当对象处于可偏向(也就是线程 ID 为0)和已偏向的状态下,调用 hashCode 计算会将使对象也无法偏向:

  • 当对象可偏向时, MarkWord 将编程未锁定状态,并且只能升级成轻量级锁
  • 当对象正处于偏向锁时, 调用 hashCode 使偏向锁强制升级为重量级锁

测试代码同上段

偏向锁内调用,共享对象 obj1 的 hashCode 方法,锁被升级为重量级锁。 JVM 内置锁 synchronized 的几种状态概述

偏向锁状态调用 wait/notify

偏向锁状态执行 obj.notify 会升级为轻量级锁。 JVM 内置锁 synchronized 的几种状态概述 调用 obj.wait(timeout) 会升级为重量级锁。 JVM 内置锁 synchronized 的几种状态概述

轻量级锁状态

如果偏向锁失败, 虚拟机并不会升级为重量级锁,它还会尝试使用一种称为轻量锁的优化手段,此时 Mark Word 的结构也会变成轻量级锁的结构,轻量级锁所适应的场景就是线程交替执行同步块的场合,如果存在同一个时间多个线程访问同一把锁的场合,就会导致轻量级锁膨胀为重量级锁。

偏向锁升级为轻量级锁

@Slf4j
public class LightweightLockTest {

    public static void main(String[] args) throws InterruptedException {
        log.debug(ClassLayout.parseInstance(new Object()).toPrintable());
        Thread.sleep(5000);
        Object obj1 = new Object();
        log.debug(ClassLayout.parseInstance(obj1).toPrintable());
        new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
                synchronized (obj1) {
                    log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
                }
                log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
            }
        }, "thread0").start();

        Thread.sleep(1000);

        new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
                synchronized (obj1) {
                    log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
                }
                log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
            }
        }, "thread1").start();

        Thread.sleep(5000);
        log.debug(ClassLayout.parseInstance(obj1).toPrintable());
    }
}

打印日志:

[01:00:56:088] [DEBUG] - com.ssm.juc.synchronized1.ThinLockTest.main(ThinLockTest.java:15) - java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

[01:01:01:099] [DEBUG] - com.ssm.juc.synchronized1.ThinLockTest.main(ThinLockTest.java:18) - java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

[01:01:01:104] [DEBUG] - com.ssm.juc.synchronized1.ThinLockTest$1.run(ThinLockTest.java:22) - start ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

[01:01:01:104] [DEBUG] - com.ssm.juc.synchronized1.ThinLockTest$1.run(ThinLockTest.java:24) - lock ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00007fae16993005 (biased: 0x0000001feb85a64c; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

[01:01:01:105] [DEBUG] - com.ssm.juc.synchronized1.ThinLockTest$1.run(ThinLockTest.java:26) - unlock ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00007fae16993005 (biased: 0x0000001feb85a64c; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

[01:01:02:119] [DEBUG] - com.ssm.juc.synchronized1.ThinLockTest$2.run(ThinLockTest.java:35) - start ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00007fae16993005 (biased: 0x0000001feb85a64c; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

[01:01:02:121] [DEBUG] - com.ssm.juc.synchronized1.ThinLockTest$2.run(ThinLockTest.java:37) - lock ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000030d7caa40 (thin lock: 0x000000030d7caa40)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

[01:01:02:128] [DEBUG] - com.ssm.juc.synchronized1.ThinLockTest$2.run(ThinLockTest.java:39) - unlock ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

[01:01:07:120] [DEBUG] - com.ssm.juc.synchronized1.ThinLockTest.main(ThinLockTest.java:44) - java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

由于日志过长无法标记,我就简单的画一个流程图吧 JVM 内置锁 synchronized 的几种状态概述

轻量级锁升级为重量级锁

需要制造竞争激烈的场景,我们可以通过线程池的方式来模拟,代码如下:


/**
 * 轻量级锁升级到重量级锁
 * <p>
 */
@Slf4j
public class ThinUpgradeFatLockTest {

    public static void main(String[] args) throws InterruptedException {
        log.debug(ClassLayout.parseInstance(new Object()).toPrintable());
        Thread.sleep(5000);
        Object obj1 = new Object();
        log.debug(ClassLayout.parseInstance(obj1).toPrintable());

        ExecutorService executeService = Executors.newFixedThreadPool(4);
        executeService.submit(new Thread(new Runnable() {
            @Override
            public void run() {
                log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
                synchronized (obj1) {
                    log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
                }
                log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
            }
        }, "thread0"));

        //Thread.sleep(1000);

        executeService.submit(new Thread(new Runnable() {
            @Override
            public void run() {
                log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
                synchronized (obj1) {
                    log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
                }
                log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
            }
        }, "thread1"));

        executeService.submit(new Thread(new Runnable() {
            @Override
            public void run() {
                log.debug("start ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
                synchronized (obj1) {
                    log.debug("lock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
                }
                log.debug("unlock ... \r\n" + ClassLayout.parseInstance(obj1).toPrintable());
            }
        }, "thread3"));

        executeService.shutdown();
        while (!executeService.isTerminated()) {
        }
        log.debug(ClassLayout.parseInstance(obj1).toPrintable());
    }


}

我们再来看看结果: 第2个线程获取锁过后,锁由偏向锁升级为轻量级锁 JVM 内置锁 synchronized 的几种状态概述 第3个线程已经采用的是重量级锁。 JVM 内置锁 synchronized 的几种状态概述

说明:为什么有时候没有升级成重量级锁?这个可能是当是 CPU 资源比较空闲,计算逻辑处理能力比较强,不需要进行锁升级,大家可以尝试多增加几个线程参与锁的争抢或者该程序多运行几次。 日志信息:

01:37:22.900 DEBUG com.ssm.juc.synchronized1.ThinUpgradeFatLockTest [main] 18 main - java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

01:37:27.918 DEBUG com.ssm.juc.synchronized1.ThinUpgradeFatLockTest [main] 21 main - java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

01:37:27.932 DEBUG com.ssm.juc.synchronized1.ThinUpgradeFatLockTest$1 [pool-2-thread-1] 27 run - start ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

01:37:27.935 DEBUG com.ssm.juc.synchronized1.ThinUpgradeFatLockTest$1 [pool-2-thread-1] 29 run - lock ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00007fbeb092b005 (biased: 0x0000001fefac24ac; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

01:37:27.940 DEBUG com.ssm.juc.synchronized1.ThinUpgradeFatLockTest$1 [pool-2-thread-1] 31 run - unlock ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00007fbeb092b005 (biased: 0x0000001fefac24ac; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

01:37:27.941 DEBUG com.ssm.juc.synchronized1.ThinUpgradeFatLockTest$2 [pool-2-thread-2] 40 run - start ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00007fbeb092b005 (biased: 0x0000001fefac24ac; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

01:37:27.945 DEBUG com.ssm.juc.synchronized1.ThinUpgradeFatLockTest$3 [pool-2-thread-3] 51 run - start ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00007fbeb092b005 (biased: 0x0000001fefac24ac; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

01:37:27.946 DEBUG com.ssm.juc.synchronized1.ThinUpgradeFatLockTest$2 [pool-2-thread-2] 42 run - lock ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000030fb147b8 (thin lock: 0x000000030fb147b8)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

01:37:27.948 DEBUG com.ssm.juc.synchronized1.ThinUpgradeFatLockTest$3 [pool-2-thread-3] 53 run - lock ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00007fbeb210d2ea (fat lock: 0x00007fbeb210d2ea)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

01:37:27.948 DEBUG com.ssm.juc.synchronized1.ThinUpgradeFatLockTest$2 [pool-2-thread-2] 44 run - unlock ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00007fbeb210d2ea (fat lock: 0x00007fbeb210d2ea)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

01:37:27.950 DEBUG com.ssm.juc.synchronized1.ThinUpgradeFatLockTest$3 [pool-2-thread-3] 55 run - unlock ... 
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

01:37:27.951 DEBUG com.ssm.juc.synchronized1.ThinUpgradeFatLockTest [main] 62 main - java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

参考资料