并发编程-synchronized锁升级原理分析

1、对象内存分布(此时说的对象为堆中开辟的内存)

对象包含:对象头(Mark Word,Klass Pointer)、实例数据、对齐填充

Mark Word:存储对象的锁状态(001无锁,101偏向锁,00轻量级锁,10重量级锁),偏向锁线程ID,轻量级锁LR指针,HashCode,GC分代年龄等,这些数据在32位和64位的虚拟机中的长度分别为4字节、8字节。

Klass Pointer:类的元数据指针,虚拟机通过这个指针确定对象是哪个类的实例,占用内存大小32位虚拟机和64位虚拟机(默认开启指针压缩)分别为4字节和4字节(使用-XX:-UseCompressedOops关闭指针压缩则为8字节)

实例数据; 包含类的属性和父类的属性信息

对齐填充:当Mark Word+Klass Pointer+实例数据的字节大小不是8的整数倍时(虚拟机要求对象起始地址必须是8的整数倍),则填充对应的字节大小

 

 

2、使用JOL工具查看对象内存布局

JOL工具依赖

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

锁升级流程图:

a、无锁状态

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

//lock对象内存布局打印
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION             VALUE
      //对象头#Mark Word#锁状态                              #锁状态#001代表锁状态为无锁   
      0     4        (object header)         01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      //对象头#Mark Word#Klass Pointer              #指针表示对象是哪个类的实例(指向运行时常量池里的class代码)
      //32位虚拟机Klass Pointer指针占用内存大小为4字节
      //64位虚拟机开启指针压缩并且内存<32G则Klass Pointer占用内存大小为4字节
      //64位虚拟机内存>32G或关闭指针压缩(-XX:-UseCompressedOops)则占用内存大小为8字节
      4     4        (object header)         00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      //实例数据
      8     4        (object header)         e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
      //对齐填充(Mark Word+Klass Pointer+实例数据!=8字节的整数倍,将自动填充) 
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

b、偏向锁-可偏向状态/匿名偏向==>偏向锁-偏向状态

public static void main(String[] args) throws InterruptedException {
    Thread.sleep(4100);
    Object lock = new Object();
    //虚拟机启动4秒后,锁状态变为偏向锁-可偏向状态
    System.out.println(ClassLayout.parseInstance(lock).toPrintable());
    synchronized (lock){    
        System.out.println("//====================进入synchronized后打印====================");
        ////进入synchronized代码块后,锁状态由偏向锁-可偏向 变为 偏向锁-偏向
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
    }
}

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                   VALUE
      //#对象头#Mark Word                                       #101 偏向锁  00000000 00000000 00000000可偏向,线程id为0
      0     4        (object header)               05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)               00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)               e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

//====================进入synchronized后打印====================
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                   VALUE
      //#对象头#Mark Word                                       #101 偏向锁  01000000 11110100 00000010 偏向的线程id
      0     4        (object header)               05 40 f4 02 (00000101 01000000 11110100 00000010) (49561605)
      4     4        (object header)               00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)               e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

c、偏向锁-可偏向状态/匿名偏向==>偏向锁-偏向状态==>轻量级锁

public class TestSynchronized {
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(4100);
        Object lock = new Object();
        TestSynchronized testSynchronized = new TestSynchronized();
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
        testSynchronized.syncLayout(lock);
        for (int i = 0; i < 1; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        testSynchronized.syncLayout(lock);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
    public void syncLayout(Object lock) throws InterruptedException {
        synchronized (lock){
            Thread.sleep(1500);
            System.out.println("=============当前线程==========="+Thread.currentThread().getId());
            System.out.println(ClassLayout.parseInstance(lock).toPrintable());
        }
    }
}

//对象内存布局打印
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                   VALUE
      //对象头#Mark Word                                        #101偏向锁 00000000 00000000 00000000表示线程id为0,偏向锁-可偏向    
      0     4        (object header)               05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)               00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)               e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

=============当前线程===========1
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                   VALUE
      //对象头#Mark Word                                        #101偏向锁 01000000 10000100 00000010表示线程id,偏向锁-偏向    
      0     4        (object header)               05 40 84 02 (00000101 01000000 10000100 00000010) (42221573)
      4     4        (object header)               00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)               e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

=============当前线程===========12
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                    VALUE
      //对象头#Mark Word                                        #00轻量级锁 11110100 10001110 00011111表示线程栈中Lock Record对象指针    
      0     4        (object header)                40 f4 8e 1f (01000000 11110100 10001110 00011111) (529462336)
      4     4        (object header)                00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

d、两个线程轻微竞争下,偏向锁-偏向状态==>轻量级锁

public class TestSynchronized {
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(4100);
        Object lock = new Object();
        TestSynchronized testSynchronized = new TestSynchronized();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    testSynchronized.syncLayout(lock);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1").start();
        Thread.sleep(1000);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    testSynchronized.syncLayout(lock);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t2").start();
    }
    public void syncLayout(Object lock) throws InterruptedException {
        System.out.println("=============线程:"+Thread.currentThread().getName()+"进入锁前=========\n"+ClassLayout.parseInstance(lock).toPrintable());
        synchronized (lock){
            System.out.println("=============线程:"+Thread.currentThread().getName()+"执行中=========\n"+ClassLayout.parseInstance(lock).toPrintable());
        }
        System.out.println("=============线程:"+Thread.currentThread().getName()+"释放锁后=========\n"+ClassLayout.parseInstance(lock).toPrintable());
    }
}

//打印对象内存布局
=============线程:t1进入锁前=========
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                  VALUE
      //对象头#Mark Word                                       #101偏向锁 00000000 00000000 00000000线程id为0,可偏向状态    
      0     4        (object header)              05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)              00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)              e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

=============线程:t1执行中=========
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                   VALUE
      //对象头#Mark Word                                       #101偏向锁 10101000 01000110 00010111 偏向状态    
      0     4        (object header)               05 a8 46 17 (00000101 10101000 01000110 00010111) (390506501)
      4     4        (object header)               00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)               e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

=============线程:t1释放锁后=========
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                    VALUE
      //对象头#Mark Word                                       #101偏向锁 10101000 01000110 00010111 偏向状态    
      0     4        (object header)                05 a8 46 17 (00000101 10101000 01000110 00010111) (390506501)
      4     4        (object header)                00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

=============线程:t2进入锁前=========
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                     VALUE
      //对象头#Mark Word                                          #101偏向锁 10101000 01000110 00010111 偏向状态    
      0     4        (object header)                 05 a8 46 17 (00000101 10101000 01000110 00010111) (390506501)
      4     4        (object header)                 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                 e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

=============线程:t2执行中=========
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                      VALUE
      //对象头#Mark Word                                          #00轻量级锁 11101111 01110110 00011000 Lock Record指针
      //                                                          hashcode会存储到Lock Record中,而Lock Record会存储到栈中
      0     4        (object header)                  90 ef 76 18 (10010000 11101111 01110110 00011000) (410447760)
      4     4        (object header)                  00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                  e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

=============线程:t2释放锁后=========
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                       VALUE
      //对象头#Mark Word                                            #01无锁 
      0     4        (object header)                   01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                   00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                   e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

3、synchronized是管程在jvm级别的实现

//=================================无锁========================================//

虚拟机启动之后,对象的锁状态默认为无锁状态

//================================偏向锁=======================================//

虚拟机默认锁延迟,虚拟机启动5秒后,对象锁状态变为匿名偏向锁

偏向锁加锁过程:

线程1到达synchronized(obj),判断锁对象状态为偏向锁(101 ),并且Object的MarkWord的ThreadId是否为null

  为null,cas设置ThreadId为当前线程id:

    cas成功,偏向锁偏向成功。

    cas失败,说明其他线程获得了偏向锁,存在锁竞争,需要撤销已获得偏向锁的线程,并把对象锁状态升级为轻量级锁(此操作需要等到安全点,也就是GC的STW才能执行) 。

  不为null,则判断线程id是否为当前线程:

    是,则为重入锁,在线程栈中创建LockRecord,cas设置displaced word为null,对象引用指向Object,更新Object的MarkWord指向新的LockRecord地址。

    否,说明其他线程获得了偏向锁,存在锁竞争,需要撤销已获得偏向锁的线程,并把对象锁状态升级为轻量级锁(此操作需要等到全局安全点,也就是GC的STW才能执行)。

偏向锁撤销,并升级为轻量级锁的具体过程 :  偏向锁撤销,更新锁对象状态为01,ThreadId为null,在线程栈创建LockRecord,复制Object的MarkWord到LR的displaced word,设置对象引用指向Object,更新Object的MarkWord指向LR地址 。      

//=================================轻量级锁加锁=================================//

轻量级锁适用线程轻微竞争的情况

轻量级锁加锁过程:

轻量级锁解锁变为无锁状态

线程到达synchronized(obj)后,判断对象锁状态是否为无锁状态(001)

是,当前线程创建LockRecord,cas设置displaced word为markWord,更新对象引用指向Object,更新markword指针指向LockRecord地址,成功则返回
否,则判断markword指针是否为null

是,当前线程创建LockRecord,cas设置displaced word为markword,更新对象引用指向Object,更新markword为LockRecord地址
否,则判断markWord指针是否为当前线程

是,则为重入锁,当前线程创建LockRecord,设置displaced word为null,更新对象引用指向Object,更新markWord指针指向LockRecord地址,成功则返回
否,则存在锁竞争,锁膨胀获取moniter对象(),执行moniterenter逻辑,执行cas获取锁,也就是设置_owner为当前线程,成功则返回,失败则继续执行多次自适应自旋+cas,成功则返回,再失败则入队_cxq,调用ParkEvent.park()线程进入BLOCKED状态,等待Trigger.unpark()唤醒

轻量级锁解锁过程:

取出栈中LockRecord的displaced word,判断是否为空

是,则为重入锁,重入次数减1,删除锁记录LockRecord。
否,cas将displaced word设置回markword,成功则解锁成功,失败存在竞争则开始膨胀,进入重量级锁解锁流程。

 

//============================升级重量级锁=================================//

重量级锁适用于多线程出现激烈激烈竞争情况

重量级锁加锁过程:

判断对象是否为无锁状态(001)

是,则获取moniter对象(cas设置_header为markword,_owner为null,对象引用指向Object),设置markword指针指向moniter,执行moniter的enter逻辑
否,直接执行moniter的enter逻辑

moniter的enter逻辑:cas抢占锁也就是更新_owner为当前线程,成功则返回,失败则继续执行多次自适应自旋cas抢占锁,当自适应失败达到一定次数后,则入队_cxq,调用ParkEvent.park()挂起当前线程(线程状态为BLOCKED,注意调用ParkEvent.park()前获取到锁并不会出现用户态和内核态的切换,性能消耗不大),等待Trigger.unpark()唤醒

重量级锁解锁过程:

判断_owner是否为当前线程

是,更新_owner为null,释放锁,调用Trigger.unpark唤醒_waitSet或_cxq队列里的线程
否,则其他线程占用该锁,直接返回

 

//============================调用Object.hashCode===========================//

1、在偏向锁-未偏向状态下,调用Object.hasCode时,由于在偏向锁状态下无法存储hashcode,此时锁状态撤销为无锁状态。
2、在偏向锁-已偏向状态下,调用Object.hashCode时,由于在偏向状态下无法存储hashCode,此时锁状态升级为轻量级锁。
3、在轻量级锁状态下,调用Object.hashCode时,此时锁升级为重量级锁。

//============================调用Object.wait================================//

1、在轻量级锁状态下,调用Object.wait(1)时,此时锁升级为重量级锁。
2、在偏向锁-已偏向状态下,调用Object.wait(1)时,此时锁状态升级为重量级锁。

//============================调用Object.notify/notifyAll=========================//

1、在偏向锁-已偏向状态下,调用Object.notify/notifyAll,此时锁状态升级为轻量级锁。
2、在轻量级锁下,调用Object.notify/notifyAll,此时锁状态不变。
 

4、锁粗化(对同一个对象反复加锁解锁,甚至加锁操作是出现在循环体中的,如果JVM检测到一连串零碎操作是针对同一对象的加锁,将会扩大锁同步范围到整个操作序列的外部)

public class TestSynchronized {
    StringBuffer stringBuffer = new StringBuffer();
    public void append(){
        //此时的appeng方法是synchronized修饰的,因此存在多次加锁解锁的过程
        //此时JVM检测到对同一个对象加锁解锁,就会将其合并成范围更大的加锁解锁
        stringBuffer.append("a").append("b");
    }
}

5、锁消除(虚拟机在JIT编译期间,通过上下文扫描,去除不可能存在共享资源竞争的锁,通过锁消除,节省毫无意义的锁请求时间)

public void append(){
    //因为stringBuffer是局部变量,因此不存在线程竞争
    StringBuffer stringBuffer = new StringBuffer();
    //此时的appeng方法是synchronized修饰的,因此存在多次加锁解锁的过程
    //但是此时的stringBuffer对象是局部变量,不存在资源竞争,因此可以把append上的synchronized消除
    stringBuffer.append("a").append("b");
}

 参考链接:https://blog.csdn.net/cx201125/article/details/130060155

posted @ 2023-06-20 15:11  西风51668  阅读(128)  评论(0)    收藏  举报