JOL探索synchronized锁-子路老师

synchronized锁:①锁实例 ②锁对象

package com.hx.zbhuang;

public class SyncExplore {

    public void test(){
        // 锁住类实例
        synchronized (this){
            System.out.println("this start");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("this end");
        }
    }

    public synchronized void test1(){
        // 锁住类实例
        System.out.println("method this");
    }

    public void test2(){
        // 锁住类对象
        synchronized (this.getClass()){
            System.out.println("Class start");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Class end");
        }
    }

    public synchronized static void test3(){
        // 锁住类对象
        System.out.println("method Class");
    }
    
    public static void main(String[] args) {
        SyncExplore syncExplore = new SyncExplore();
        new Thread(syncExplore::test,"t1").start();
        new Thread(syncExplore::test1,"t1").start();
        new Thread(syncExplore::test2,"t1").start();
        new Thread(SyncExplore::test3,"t1").start();
    }

}

持有相同锁的代码块同步执行

synchronized锁标识为对象头的运行时元数据的线程持有锁

http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

每个GC托管堆对象开始处的公共结构。(每个oop指向一个对象头)包括关于堆对象的布局、类型、GC状态、同步状态和标识哈希代码的基本信息。由两个词组成。在数组中,它后面紧跟一个长度字段。Java对象和VM内部对象都有一个通用的对象头格式。

每个对象头的第一个字段。通常是一组位域,包括同步状态和标识哈希码。也可以是指向同步相关信息的指针(具有特征低位编码)。在GC期间,可能包含GC状态位。

每个对象头的第二个字段。指向另一个描述原始对象布局和行为的对象(元对象)。对于java对象,“KLASS”包含C++样式“VTABLE”。

引入依赖:

      <!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>

创建一个对象查看对象头信息:

package com.hx.zbhuang;

public class DouFuDan {
    boolean flag = false;
}
package com.hx.zbhuang;

import sun.misc.Unsafe;
import java.lang.reflect.Field;

/**
 * 对象头hash由二进制转换为十六进制
 */
public class HashUtil {
    public static void countHash(Object object) throws NoSuchFieldException, IllegalAccessException {
        // 反射获取theUnsafe属性(new Unsafe())
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);
        long hashCode = 0;
        //获取去除MarkWord前8位(锁标志位)后56位二进制(hashCode)
        for (long index = 7; index > 0; index--) {
            hashCode |= (unsafe.getByte(object, index) & 0xFF) << ((index - 1) * 8);
        }
        //转换为十六进制
        String code = Long.toHexString(hashCode);
        System.out.println("util-----------0x"+code);

    }
}
package com.hx.zbhuang;

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

public class JOLExploreSync1 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        DouFuDan douFuDan = new DouFuDan();
        // 获取jvm信息
        System.out.println(VM.current().details());
        System.out.println("before hash");
        // 对象进行hashCode
        System.out.println(Integer.toHexString(douFuDan.hashCode()));
        // 获取对象头的hashCode值
        HashUtil.countHash(douFuDan);
        System.out.println("after hash");
        // 获取对象信息
        System.out.println(ClassLayout.parseInstance(douFuDan).toPrintable());
    }
}

可以看出对象由16byte存储,对象头占12byte,实例数据占1byte,对齐填充站3byte,其中对象头中运行时元数据占8byte,类型指针占4byte,运行时元数据8byte中第1个byte存储分带年龄,偏向锁标识,对象状态,第2-5个byte为hashCode值和1bit存储unse,第6-8byte为unuse,

运行时元数据8byte中第1个byte存储:

DouFuDan对象未持有锁,偏向锁标识为0,对象状态为01为无锁

测试持有锁:

package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class JOLExploreSync2 {
    public static void main(String[] args) {
        DouFuDan douFuDan = new DouFuDan();
        System.out.println("before lock:" + ClassLayout.parseInstance(douFuDan).toPrintable());
        synchronized (douFuDan) {
            System.out.println("locking:" + ClassLayout.parseInstance(douFuDan).toPrintable());
        }
        System.out.println("after lock:" + ClassLayout.parseInstance(douFuDan).toPrintable());    }
}

DouFuDan对象被加锁后偏向锁标志位为0,对象状态位为00,大佬们说这个是轻量锁,而非偏向锁为什么呢?

(轻量级锁尝试在应用层面解决线程同步 问题,而不触发操作系统的互斥操作,轻量级锁减少多线程进入互斥的几率,不能代替互斥)

加个休眠时间

package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class JOLExploreSync2 {
    public static void main(String[] args) {
        try {
            Thread.sleep(5000);
            DouFuDan douFuDan = new DouFuDan();
            System.out.println("before lock:" + ClassLayout.parseInstance(douFuDan).toPrintable());
            synchronized (douFuDan) {
                System.out.println("locking:" + ClassLayout.parseInstance(douFuDan).toPrintable());
            }
            System.out.println("after lock:" + ClassLayout.parseInstance(douFuDan).toPrintable());

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

发现偏向锁标志位为1,对象状态位为01,为大佬所说的偏向锁标识位,这是为什么呢?

查看https://www.oracle.com/java/technologies/java-tuning.html#section4.2.5

Enables a technique for improving the performance of uncontended synchronization. An object is "biased" toward the thread which first acquires its monitor via a monitorenter bytecode or synchronized method invocation; subsequent monitor-related operations performed by that thread are relatively much faster on multiprocessor machines. Some applications with significant amounts of uncontended synchronization may attain significant speedups with this flag enabled; some applications with certain patterns of locking may see slowdowns, though attempts have been made to minimize the negative impact.

启用一种提高无争用同步性能的技术。一个对象“偏向”于线程,该线程首先通过monitorenter字节码或同步方法调用获取其监视器;在多处理器计算机上,由该线程执行的与监视器相关的后续操作相对要快得多。一些具有大量非竞争同步的应用程序在启用此标志时可能会获得显著的加速;某些具有某些锁定模式的应用程序可能会出现减速,尽管已经尝试将负面影响降至最低。

在另一篇探索synchronized偏向锁与重量锁区别中修改pthread_mutex_lock发现启动main进程会有大量线程操作os加锁,如果开始就加上偏向锁,就会导致减速,可能是jvm底层优化,直接上了轻量锁

现在我们注释休眠代码,添加参数-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0,将偏向锁延迟时间设置为0发现结果偏向锁标志位为1,对象状态位为01,应该是jvm做了优化。

添加参数查看延迟加载时间:

修改大于4000ms锁竞争测试:查看结果一致变为偏向锁

修改DouFuDan

package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class DouFuDan {
    public synchronized void lock(){
        System.out.println("method lock locking:" + ClassLayout.parseInstance(this).toPrintable());
    }
}
package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class JOLExploreSync3 {
    public static void main(String[] args) {
        DouFuDan douFuDan = new DouFuDan();
        System.out.println("before lock:" + ClassLayout.parseInstance(douFuDan).toPrintable());
        new Thread() {
            public void run() {
                synchronized (douFuDan) {
                    try {
                        Thread.sleep(6000);
                        System.out.println("main DouFuDan locking:" + ClassLayout.parseInstance(douFuDan).toPrintable());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
        System.out.println("after main DouFuDan locking:" + ClassLayout.parseInstance(douFuDan).toPrintable());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        douFuDan.lock();
    }
}

竞争后偏向锁标志位为0,对象状态位为10变为偏向锁标志位为0,对象状态位为10,升级为重量级锁

查看http://hg.openjdk.java.net/jdk8u/jdk8u60/hotspot/file/37240c1019fd/src/share/vm/oops/markOop.hpp

发现偏向锁hashCode位置存储的是javaThread,这导致计算hashCode之后不能设置为偏向锁,不然计算出来的hashCode值会被覆盖为javaThread,导致两次计算出来的hashCode不一致

休眠五秒后计算hashCode加锁

package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class JOLExploreSync4 {
    public static void main(String[] args) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        DouFuDan douFuDan = new DouFuDan();
        douFuDan.hashCode();
        synchronized (douFuDan) {
            System.out.println("locking:" + ClassLayout.parseInstance(douFuDan).toPrintable());
        }
    }
}

发现偏向锁标志位为0,对象状态位为00,轻量级锁

总结为

无锁偏向锁标志位为0,对象状态位为01
偏向锁偏向锁标志位为1,对象状态位为01
轻量级锁偏向锁标志位为0,对象状态位为00
重量级锁偏向锁标志位为0,对象状态位为10

 

 

 

 

 

查看各种锁的时间:

修改豆腐蛋

package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class DouFuDan {
    int i;
    public synchronized void add(){
        if(i==0) {
            System.out.println("method add"+ ClassLayout.parseInstance(this).toPrintable());
        }
        i++;
    }
}

偏向锁

package com.hx.zbhuang;

public class JOLExploreSync5 {
    public static void main(String[] args) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        DouFuDan douFuDan = new DouFuDan();
        long start = System.currentTimeMillis();
        for(int i=0;i<1000000000L;i++){
            douFuDan.add();
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end - start));
    }
}

轻量级锁

package com.hx.zbhuang;

public class JOLExploreSync6 {
    public static void main(String[] args) {
        DouFuDan douFuDan = new DouFuDan();
        long start = System.currentTimeMillis();
        for(int i=0;i<1000000000L;i++){
            douFuDan.add();
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end - start));
    }
}

重量级锁

修改DouFuDan

package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class DouFuDan {
    public synchronized void open(){
        if(JOLExploreSync7.downLatch.getCount()==0) {
            System.out.println("method add"+ ClassLayout.parseInstance(this).toPrintable());
        }
        JOLExploreSync7.downLatch.countDown();
    }
}
package com.hx.zbhuang;

import java.util.concurrent.CountDownLatch;

public class JOLExploreSync7 {
    static CountDownLatch downLatch = new CountDownLatch(1000000000);
    public static void main(String[] args) {
        DouFuDan douFuDan = new DouFuDan();
        long start = System.currentTimeMillis();
        for(int i=0;i<2;i++){
            new Thread() {
                @Override
                public void run() {
                    while(downLatch.getCount()>0) {
                        douFuDan.open();
                    }
                }
            }.start();
        }
        try {
            downLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end - start));
    }
}

锁类型偏向锁轻量级锁重量级锁
消耗时间/ms46571642564279

 

 

 

wait解锁

package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class JOLExploreSync8 {
    public static void main(String[] args) {
        DouFuDan douFuDan=new DouFuDan();
        System.out.println("before lock:" + ClassLayout.parseInstance(douFuDan).toPrintable());
        new Thread(){
            @Override
            public void run() {
                synchronized (douFuDan){
                    synchronized (douFuDan){
                        System.out.println("before wait:" + ClassLayout.parseInstance(douFuDan).toPrintable());
                        try {
                            douFuDan.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("after wait:" + ClassLayout.parseInstance(douFuDan).toPrintable());
                    }
                }
            }
        }.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (douFuDan){
            System.out.println("update:" + ClassLayout.parseInstance(douFuDan).toPrintable());
            douFuDan.notify();
        }
    }
}

开始线程启动为轻量级锁,执行await进行等待释放锁,主线程执行获取锁升级为重量锁并唤醒子线程,后面得到的锁为重量锁。

 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值