java锁膨胀过程

Posted 哎喔别走

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java锁膨胀过程相关的知识,希望对你有一定的参考价值。

        今天看了一个视频教程,其中讲到了锁膨胀的过程,这个课程通过一个java main很容易的让人理解synchronized的优化以及锁膨胀的过程。在上代码之前先上张对象头markword的图片如下:

代码如下(主线程再加上新起的两个线程,三个线程同步锁stepNum导致锁升级,由偏向锁到轻量级锁,到重量级锁):

import org.openjdk.jol.info.ClassLayout;

public class LockEvolutionShow {
    public static void main(String[] args) {
        Integer initNum = new Integer("1");// 初始化一个对象,默认是无锁状态(状态为non-biasable)最后三位是001,注意打印的结果是16进制,转为二进制即可
        System.out.println("【non-biasable】===对象头MarkWord末尾三位【001】=" + ClassLayout.parseInstance(initNum).toPrintable());

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Integer stepNum = new Integer("2");// JVM默认启动后等待3s后就会把对象标记为偏向锁(状态为biasable),此时打印结果可以看到后三位是101。,注意打印的结果是16进制,转为二进制即可
        System.out.println("【biasable】===对象头MarkWord末尾三位【101】=" + ClassLayout.parseInstance(stepNum).toPrintable());

        // 添加同步原语后,可以看到对象被标记为偏向锁,注意打印的后三位是101(状态为biased),注意打印的结果是16进制,转为二进制即可
        synchronized (stepNum) {
            System.out.println(
                    "【biased lock】===对象头MarkWord末尾三位【101】=" + ClassLayout.parseInstance(stepNum).toPrintable());
        }
        System.out.println(// 退出同步块后,可以看到对象依然被标记为偏向锁(状态为biased),并未清除,此时打印结果可以看到后三位是101。,注意打印的结果是16进制,转为二进制即可
                "【biased lock release]===对象头MarkWord末尾三位【101】=" + ClassLayout.parseInstance(stepNum).toPrintable());

        new Thread(() -> {// 启动了一个线程,线程的并发特性(上面的主线程和这个新起的线程并发,同步锁stepNum),导致锁升级,这时候升级为轻量级锁(状态为thin
                          // lock),此时打印结果可以看到后两位是00。,注意打印的结果是16进制,转为二进制即可
            synchronized (stepNum) {
                System.out.println(
                        "【thin lock】===对象头MarkWord末尾两位【00】=" + ClassLayout.parseInstance(stepNum).toPrintable());
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 注意这个线程保持锁至少5s,导致下面又新起的线程同步锁stepNum,再次锁升级,由轻量级锁thin变为重量级fat,此时打印结果后两位是10。
                System.out.println("【thin lock -> fat lock】===对象头MarkWord末尾两位【10】="
                        + ClassLayout.parseInstance(stepNum).toPrintable());
            }
        }).start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            synchronized (stepNum) {
                ////注意这个线程同步锁stepNum,导致锁升级,由轻量级锁thin变为重量级fat,此时打印结果后两位是10。
                System.out.println(
                        "【fat lock】===对象头MarkWord末尾两位【10】=" + ClassLayout.parseInstance(stepNum).toPrintable());
            }
        }).start();
    }
}

【non-biasable】===对象头MarkWord末尾三位【001】=java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf80022ad
12 4 int Integer.value 1
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

【biasable】===对象头MarkWord末尾三位【101】=java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)
8 4 (object header: class) 0xf80022ad
12 4 int Integer.value 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

【biased lock】===对象头MarkWord末尾三位【101】=java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00000000025ba005 (biased: 0x00000000000096e8; epoch: 0; age: 0)
8 4 (object header: class) 0xf80022ad
12 4 int Integer.value 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

【biased lock release]===对象头MarkWord末尾三位【101】=java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00000000025ba005 (biased: 0x00000000000096e8; epoch: 0; age: 0)
8 4 (object header: class) 0xf80022ad
12 4 int Integer.value 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

【thin lock】===对象头MarkWord末尾两位【00】=java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000001f08f6f0 (thin lock: 0x000000001f08f6f0)
8 4 (object header: class) 0xf80022ad
12 4 int Integer.value 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

【thin lock -> fat lock】===对象头MarkWord末尾两位【10】=java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000001c0f7b4a (fat lock: 0x000000001c0f7b4a)
8 4 (object header: class) 0xf80022ad
12 4 int Integer.value 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

【fat lock】===对象头MarkWord末尾两位【10】=java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000001c0f7b4a (fat lock: 0x000000001c0f7b4a)
8 4 (object header: class) 0xf80022ad
12 4 int Integer.value 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total 

PS:头部markword 8个字节,初始状态【fat lock】的value是16进制0x000000001c0f7b4a ,转为二进制是11100000011110111101101001010,末尾是10对照上面的markword图片可知是重量级锁。

 

java-锁膨胀的过程

先来看个奇怪的demo

public class A {
    int i=0;
   // boolean flag =false;
    public synchronized void parse(){
        i++;
        JOLExample6.countDownLatch.countDown();
    }
}

睡眠5秒,测试

public class JOLExample3 {
   static A a;
    public static void main(String[] args) throws Exception {
        Thread.sleep(5000);
        a= new A();
        //a.hashCode();
        out.println("befor lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());//无锁:偏向锁?
        synchronized (a){
            out.println("lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }

        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

我注释的那行代码是什么锁?看下结果

 可以看出,没有线程持有锁的时候,是可偏向状态

 

 然后我们把睡眠的代码注释掉,再测试一下

//Thread.sleep(5000);

看下结果

 再看个两个线程的demo

首先是两个线程交替执行:

public class JOLExample10 {
   static A a;
    public static void main(String[] args) throws Exception {
        a= new A();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    out.println("t1 lock ing");
                    out.println(ClassLayout.parseInstance(a).toPrintable());
                }
            }
        };
        t1.start();
       Thread.sleep(10000);//睡眠10秒,让main线程和t1线程交替执行
        synchronized (a){//a b c c+++
            out.println("main lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

看下结果

 

 可以看出,交替执行时,是轻量锁

我们把睡眠的代码注释掉

//Thread.sleep(5000);//睡眠10秒,让main线程和t1线程交替执行

再次测试,

public class JOLExample10 {
   static A a;
    public static void main(String[] args) throws Exception {
        a= new A();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    out.println("t1 lock ing");
                    out.println(ClassLayout.parseInstance(a).toPrintable());
                }
            }
        };
        t1.start();
       //Thread.sleep(5000);//睡眠10秒,让main线程和t1线程交替执行
        synchronized (a){//a b c c+++
            out.println("main lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
        Thread.sleep(5000);//睡眠10秒
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

 

看下结果

 

 

自旋

自旋一段时间,可以理解为空转,时间很短,具体时间需要看jvm源码,如果在自旋时间内拿到了锁,就不再膨胀,如果还是拿不到锁,则膨胀为重量锁,如下

public static void main(String[] args) throws Exception {
        a= new A();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    out.println("t1 lock ing");
                    out.println(ClassLayout.parseInstance(a).toPrintable());
                }
            }
        };
        t1.start();
       Thread.sleep(1670);//睡眠10秒,让main线程和t1线程交替执行
        synchronized (a){//自旋一段时间,可以理解为时间很短,具体时间需要看jvm源码,如果在自旋时间内拿到了锁,就不再膨胀,如果还是拿不到锁,则膨胀为重量锁
            out.println("main lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }

        Thread thread2 = new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    out.println("t2 lock ing");
                    out.println(ClassLayout.parseInstance(a).toPrintable());
                }
            }
        };
        thread2.start();
        /*Thread.sleep(10);
        synchronized (a){
            out.println("main lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }*/
        //Thread.sleep(5000);//睡眠10秒,让main线程和t1线程交替执行
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }

如果调用wait方法,则立即变成重量锁

看下demo

public class JOLExample11 {
    static A a;
    public static void main(String[] args) throws Exception {
        //Thread.sleep(5000);
        a = new A();
        out.println("befre lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());

        Thread t1= new Thread(){
            public void run() {
                synchronized (a){
                    try {
                        synchronized (a) {
                            System.out.println("before wait");
                            out.println(ClassLayout.parseInstance(a).toPrintable());
                            a.wait();//如果调用wait方法,则立即变成重量锁
                            System.out.println(" after wait");
                            out.println(ClassLayout.parseInstance(a).toPrintable());
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
        Thread.sleep(7000);
        synchronized (a) {
            a.notifyAll();
        }
    }
}

看下结果

 

 我们再看个synchronized膨胀的奇怪特性

让偏向锁无延迟启动

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

 

public class JOLExample12 {
    static List<A> list = new ArrayList<A>();
    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread() {
            public void run() {
                for (int i=0;i<10;i++){
                    A a = new A();
                    synchronized (a){
                        System.out.println("111111");
                        list.add(a);
                    }
                }

            }

        };
        t1.start();
        t1.join();
        out.println("befre t2");
        //偏向
        out.println(ClassLayout.parseInstance(list.get(1)).toPrintable());
        Thread t2 = new Thread() {
            int k=0;
            public void run() {
                for(A a:list){
                   synchronized (a){
                       System.out.println("22222");
                       if (k==4){
                           out.println("t2 ing");
                           //轻量锁
                           out.println(ClassLayout.parseInstance(a).toPrintable());

                       }
                   }
                    k++;
                }

            }
        };
        t2.start();
    }
}

t1线程new10个对象,t2线程取第五个,看下结果

 

 我们把new对象的数量改一下,改成20个,再来试一下

public class JOLExample12 {
    static List<A> list = new ArrayList<A>();
    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread() {
            public void run() {
                for (int i=0;i<20;i++){
                    A a = new A();
                    synchronized (a){
                        System.out.println("111111");
                        list.add(a);
                    }
                }

            }

        };
        t1.start();
        t1.join();
        out.println("befre t2");
        //偏向
        out.println(ClassLayout.parseInstance(list.get(1)).toPrintable());
        Thread t2 = new Thread() {
            int k=0;
            public void run() {
                for(A a:list){
                   synchronized (a){
                       System.out.println("22222");
                       if (k==19){
                           out.println("t2 ing");
                           //轻量锁
                           out.println(ClassLayout.parseInstance(a).toPrintable());

                       }
                   }
                    k++;
                }

            }
        };
        t2.start();
    }
}

这里我们取第20个对象,查看对象头信息

 

 我们可以看到,居然重偏向了,这里是jvm做的优化,20次以后就会冲偏向,小于20次时膨胀为轻量锁

这里我们称之为批量偏向,下面我们看下这个的原理

 

 最后总结一下:轻量锁释放的时候将mark word重置为无锁状态,附上网络上的图

 

以上是关于java锁膨胀过程的主要内容,如果未能解决你的问题,请参考以下文章

java 从hotspot底层对象结构理解锁膨胀升级过程

java-锁膨胀的过程

synchronized锁膨胀过程验证

多线程高并发之Synchronized锁及其膨胀

Java 有什么锁

JDK源码Synchronized关键字原理,和锁的膨胀过程