synchronized锁原理分析(二、锁的膨胀过程--全网最完整流程图)

继续承接上一篇博客一、从Java对象头看synchronized锁的状态

先通过几个案例,从结果直观的展示锁是如何膨胀的

不想看案例,直接到最后看流程图


案例1(无锁,不可偏向状态)
import org.openjdk.jol.info.ClassLayout;
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        A a = new A();
        ///  理论上说这里应该是无锁状态
        System.out.println(ClassLayout.parseInstance(a).toPrintable()); 
    }
}

运行结果:
synchronized锁原理分析(二、锁的膨胀过程--全网最完整流程图)_第1张图片
mark_word 最后3bit 是 001 ,表示 无锁,且不可偏向

案例2(HashCode存哪里)
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        A a = new A();
        System.out.println("a对象的hashcode:16进制:"+Integer.toHexString(a.hashCode()));
        System.out.println("a对象的hashcode:2进制:"+Integer.toBinaryString(a.hashCode()));
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

运行结果:
synchronized锁原理分析(二、锁的膨胀过程--全网最完整流程图)_第2张图片
可以看出: mark_word 最后3bit 是 001 ,表示 无锁,且不可偏向。这时候一旦进行对象的hashcode计算,那么hashcode就会存储在mark_word 的第26–56的位置,共占 31个bit,前面25bit为0,不存任何信息。
由此可见,对象的hashcode是延迟加载的。

案例3(无锁,可偏向状态)
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(5000);
        A a = new A();
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

运行结果:
synchronized锁原理分析(二、锁的膨胀过程--全网最完整流程图)_第3张图片
从运行结果可以看出:mark_word 最后3bit 是 101,而且前面54bit为0,说明a是一个无锁且可偏向状态。
为什么sleep了5秒钟,a对象的状态就和之前的不一样了呢?从一个不可偏向的无锁变成了一个可偏向的无锁!

JVM的偏向锁其实是可以进行开启和关闭的,偏向锁虽然是默认启用的,但是它在应用程序启动几秒钟之后才激活
这就解释了,为什么main方法中如果sleep一段时间,new出来的a对象,是一个无锁的可偏向状态,因为这个时候jvm已经激活了偏向锁机制。
我们可以通过使用JVM参数来关闭延迟 -XX:BiasedLockingStartupDelay=0,这样一来,程序在启动的时候new出来的对象就是可偏向状态的无锁对象。
当然还可以JVM参数关闭偏向锁-XX:-UseBiasedLocking

通过JVM的 参数 -XX:+PrintFlagsFinal可以查看出 偏向锁的启动延迟是4000微妙也就是4秒钟。
synchronized锁原理分析(二、锁的膨胀过程--全网最完整流程图)_第4张图片

案例4(神奇的hashcode1)
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(5000);/// sleep 一段时间是为了让JVM激活偏向锁机制

        A a = new A(); /// 现在new出来的对象默认是  可偏向的无锁状态  (101)
        System.out.println("刚new出来:");
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
        /// 这里我们计算一下a对象的hashcode,然后再看一下a的状态是什么?
        a.hashCode();
        System.out.println("计算过hashcode后:");
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

运行结果:
synchronized锁原理分析(二、锁的膨胀过程--全网最完整流程图)_第5张图片
从上面运行结果可以看出:计算一个对象的hashcode会使得一个对象从 可偏向无锁变为不可偏向无锁

插播一个工具类

为了后面的测试方便,写了一个工具类,只输出java对象头的mark_word 部分而且对大小端进行了转换

package com.com.kinyang.jol;

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import org.openjdk.jol.vm.VirtualMachine;

/**
 * @author KinYang.Lou
 * @date 2020/3/27 8:30 下午
 */
public class Print64JVMHeader {

    static final String ISEMPTY = "00000000 00000000 00000000 00000000 00000000 00000000 000000";
    /**
     * 打印对象头的 mark word 信息
     * @param instance
     */
    static void toPrintMarkWord(Object instance){
        ClassLayout classLayout = ClassLayout.parseInstance(instance);
        VirtualMachine vm = VM.current();
        /// 获取 对象头大小(字节)
        long headerSize = (long) classLayout.headerSize();
        // 这里只打印 mark_word 部分
        if (headerSize<8)
            return;
        long markWordSize = 8;
        String markWord = "";
        for(long off = 0L; off < markWordSize; off += 1L) {
            byte aByte = vm.getByte(instance, off); // 取一个字节
            markWord = toBinary(aByte)+" "+markWord;
        }
        markWord = markWord.trim();
        int length = markWord.length();
        String b2 = markWord.substring(length-2);  // 末2位
        String e3 = markWord.substring(length-3,length-2);/// 倒数第三位
        String i54 = markWord.substring(0,60); /// 去前面54bit数据
        String lockType = "";
        switch (b2){
            case "11":
                lockType = "被GC标记";
                break;
            case "10":
                lockType = "重量级锁";
                break;
            case "00":
                lockType = "轻量级锁";
                break;
            case "01":
            {
                if (e3.equals("0")){
                    lockType = "不可偏向&无锁";
                }else{
                    if(ISEMPTY.equals(i54)){
                        lockType = "可偏向&无锁";
                    }else {
                        lockType = "偏向锁";
                    }
                }
                break;
            }
            default:
                lockType = "出现错误";
        }
        System.out.println(instance.getClass().getName()+" 对象的mark_word:");
        System.out.println(markWord + " ---  ("+lockType+")");


    }
    private static String toBinary(byte x){
        String s = Integer.toBinaryString((x & 0xFF) + 0x100).substring(1);
        return s;
    }
}


案例5(神奇的hashcode2)
package com.com.kinyang.jol;

import org.openjdk.jol.info.ClassLayout;

/**
 * @author KinYang Lau
 * @date 2020/3/25 8:49 下午
 */
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(5000);/// sleep 一段时间是为了让JVM激活偏向锁机制
        ClassLayout classLayout = ClassLayout.parseInstance(Demo1.class);

        A a = new A(); /// 现在new出来的对象默认是  可偏向的无锁状态  (101)
        System.out.println("a 刚new出来:");
        Print64JVMHeader.toPrintMarkWord(a);
        synchronized (a){
            System.out.println("a 在synchronized代码块中:");
            Print64JVMHeader.toPrintMarkWord(a);
        }
        System.out.println("a 退出synchronized代码块:");
        Print64JVMHeader.toPrintMarkWord(a);
        a.hashCode();
        System.out.println("a 经过一次hashcode计算:");
        Print64JVMHeader.toPrintMarkWord(a);
    }
}

运行结果
synchronized锁原理分析(二、锁的膨胀过程--全网最完整流程图)_第6张图片
根据上图的运行结果可以看出:偏向锁在经过hashcode后,会变成不可偏向的无锁状态

案例5(神奇的hashcode3)
package com.com.kinyang.jol;

import org.openjdk.jol.info.ClassLayout;

/**
 * @author KinYang Lau
 * @date 2020/3/25 8:49 下午
 */
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(5000);/// sleep 一段时间是为了让JVM激活偏向锁机制
        ClassLayout classLayout = ClassLayout.parseInstance(Demo1.class);

        A a = new A(); /// 现在new出来的对象默认是  可偏向的无锁状态  (101)
        System.out.println("a 刚new出来:");
        Print64JVMHeader.toPrintMarkWord(a);
        synchronized (a){
            System.out.println("a 在synchronized代码块中,未进行hashcode计算:");
            Print64JVMHeader.toPrintMarkWord(a);
            System.out.println("a对象的hashcode:2进制: "+Integer.toBinaryString(a.hashCode()));
            System.out.println("a synchronized代码块中,经过一次hashcode计算:");
            Print64JVMHeader.toPrintMarkWord(a);
        }
        System.out.println("a 退出synchronized代码块:");
        Print64JVMHeader.toPrintMarkWord(a);
    }
}

运行结果:
synchronized锁原理分析(二、锁的膨胀过程--全网最完整流程图)_第7张图片
从上图的运行结果可以看出来,如果在偏向锁未释放阶段,进行hashcode计算的话,锁会直接升级为重量级锁。不知道这里为什么不升级到轻量级锁,而直接到了重量级锁?

案例6(偏向锁 升级到 轻量级锁)
package com.com.kinyang.jol;

import org.openjdk.jol.info.ClassLayout;

/**
 * @author KinYang Lau
 * @date 2020/3/25 8:49 下午
 */
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(5000);/// sleep 一段时间是为了让JVM激活偏向锁机制
        ClassLayout classLayout = ClassLayout.parseInstance(Demo2.class);

        A a = new A(); /// 现在new出来的对象默认是  可偏向的无锁状态  (101)
        System.out.println("a 刚new出来:");
        Print64JVMHeader.toPrintMarkWord(a);
        synchronized (a){
            System.out.println("\n 在 主线程中对 a 进行synchronized加锁:");
            Print64JVMHeader.toPrintMarkWord(a);
        }
        System.out.println("\n a 退出线程的synchronized代码块:");
        Print64JVMHeader.toPrintMarkWord(a);
        /// 启动一个线程,对a进行加锁
        new Thread("T1"){
            @Override
            public void run() {
                synchronized (a){
                    System.out.println("\n 进入T1线程,对a进行synchronized加锁:");
                    Print64JVMHeader.toPrintMarkWord(a);
                }
                System.out.println("\n 退出T1线程代码块:");
                Print64JVMHeader.toPrintMarkWord(a);
            }
        }.start();
    }
}

运行结果
synchronized锁原理分析(二、锁的膨胀过程--全网最完整流程图)_第8张图片

分析运行结果,一个偏向锁如果进行一次其他线程的加锁,会膨胀为轻量级锁。
注意,这里的一个前提条件,第二次的加锁必须不能和第一次加锁存在竞争关系,否则就会直接膨胀为重量级锁。
仔细分析上面的代码,第二次的T1线程在进行加锁的时候, 主线程的 加锁代码块已经执行完毕,锁t1线程和主线程不存在竞争关系,所以这里的锁只会膨胀到轻量级锁。
如果两次加锁存在竞争关系,那么锁会膨胀到重量级锁,继续看下面的例子。

案例6(偏向锁 升级到 重量级锁)
package com.com.kinyang.jol;

import org.openjdk.jol.info.ClassLayout;

import java.util.concurrent.TimeUnit;

/**
 * @author KinYang Lau
 * @date 2020/3/25 8:49 下午
 * 偏向锁 ----> 重量级锁
 */
public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(5000);/// sleep 一段时间是为了让JVM激活偏向锁机制
        ClassLayout classLayout = ClassLayout.parseInstance(Demo3.class);

        A a = new A(); /// 现在new出来的对象默认是  可偏向的无锁状态  (101)
        System.out.println("a 刚new出来:");
        Print64JVMHeader.toPrintMarkWord(a);
        Thread t1 = new Thread("T1"){
            @Override
            public void run() {
                synchronized (a){
                    try {
                        System.out.println("\n 进入T1线程,对a进行synchronized加锁:");
                        Print64JVMHeader.toPrintMarkWord(a);
                        System.out.println("T1 进入睡眠,暂且不释放锁");
                        TimeUnit.SECONDS.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        Thread t2 = new Thread("T2"){
            @Override
            public void run() {
                synchronized (a){
                    try {
                        System.out.println("\n 进入T2线程,对a进行synchronized加锁:");
                        Print64JVMHeader.toPrintMarkWord(a);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        System.out.println("让 t2 比 t1 晚启动2秒,造成t2线程与t1线程行程竞争关系");
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        t2.start();
        TimeUnit.SECONDS.sleep(15);
    }
}

运行结果:
synchronized锁原理分析(二、锁的膨胀过程--全网最完整流程图)_第9张图片
分析这次结果,偏向锁如果出现一次线程直接的竞争,会直接膨胀为重量级锁。

总结一下:偏向锁 如果是在多个线程之间交替执行,不竞争,那么锁会膨胀为轻量级锁,如果存在线程之间竞争,那么会膨胀为重量级锁。

锁膨胀流程图(下面有讲解)

上面的几个案例,可以让直观的看到锁膨胀结果。但是无法看到锁膨胀的过程。
下面通过一个流程图进行锁膨胀的流程解析。

你可能感兴趣的:(Java锁)