并发编程之-synchronize原理

并发编程之-synchronize原理

前言:准备记录一下并发编程的学习,做个笔记加深印象。并发编程学习的路神的视频。

一、为什么学习java的对象头?

因为要研究java的内置锁synchronize,就得去研究java的头对象,所谓锁就是给对象一个标识,而这个标识便是存储在对象头中。

二、对象头的分析

2.1通过jol分析java的对象头布局

添加maven依赖

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

打印对象头信息

/**
*创建一个测试对象Header
*/
public class Header {
//    int a;
}

/**
*创建测试类
*/
public class TestDemo {
static Header header = new Header();

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

/**

--------------------------------------------------------------------
运行结果
--------------------------------------------------------------------
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.example.thread.Header object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


--------------------------------------------------------------------
分析结果
--------------------------------------------------------------------

可以看到对象一共16byte,object header是4*3=12byte,还有4byte是对齐字节。现在可以看到当前对齐的4的字节是loss due to the next object alignment,就是说由于要和下一个对象对齐产生了丢失。Space losses: 0 bytes internal + 4 bytes external = 4 bytes total,空间损失也是4byte。
*/

我们修改一下header对象:

/**
*分别在Header中添加 int boolean long,并执行查看结果
*/
public class Header {
    int a;
}
/**
--------------------------------------------------------------------
运行结果
--------------------------------------------------------------------
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.example.thread.Header object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4    int Header.a                                  0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

--------------------------------------------------------------------
分析结果
--------------------------------------------------------------------
整个对象的大小没变还是16byte,其中对象头是12byte,int a对象实例数据是4byte,空间损失为0byte。

*/
public class Header {
    boolean b;
}
/**
--------------------------------------------------------------------
执行结果
--------------------------------------------------------------------

# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.example.thread.Header object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     1   boolean Header.b                                  false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

--------------------------------------------------------------------
分析结果
--------------------------------------------------------------------
整个对象的大小没变还是16byte,其中对象头是12byte,boolean b字段占用1byte,剩下3byte是对齐字节。空间损失是3byte。
*/

public class Header {
    long c;
}
/**
--------------------------------------------------------------------
执行结果
--------------------------------------------------------------------

# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.example.thread.Header object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (alignment/padding gap)                  
     16     8   long Header.c                                  0
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

--------------------------------------------------------------------
执行结果
--------------------------------------------------------------------
整个对象的大小没变还是24byte,其中对象头是12byte,4byte的对齐字节,8byte的实例数据。空间损失4byte。这是因为默认的对象头的空间大小不能存储下8byte的long类型,所以申请了新的内存空间。从offset这个地址偏移量也可以看出。
*/

2.2 大小端模式

要分析对象头的存储,首先的明白什么是大小端存储模式。在3.3中会用到,简单记录一下小端模式:

数据转换为二进制后,高字节存在高地址,低字节存在低地址。

                                                                                                                                             图1

2.3 对象头的规范

具体的解释openjdk可以看官方文档:https://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

                                                                        图2

object header

Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object's layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format.

openjdk的文档中描述,java头对象包括了2个word,包含堆对象的布局、类型、GC状态、同步状态和标识哈希码。

klass pointer

The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original object. For Java objects, the "klass" contains a C++ style "vtable".

mark word

The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.

主要包括:mark word和klass point。可以看出和对象同步状态相关的主要是在mark word中。

                                                                                                                     图3

以下对照路神的图做代码分析,解释以下上图,意思是java的对象头在不同状态下会有不同的表现形式,主要有三种状态:无锁状态、加锁状态和gc标记状态。

可以理解java当中的锁是对对象头锁状态的修改,如果修改成功则进入同步代码块。

java中的锁大致可分为:偏向锁、轻量级锁、重量级锁三种锁状态。

 

2.4 代码分析mark word

public class TestDemo {
    static Header header = new Header();

    public static void main(String[] args) {
        /**
         * 001 0-不能偏向 01-偏向锁 解释加了hashcode之后为什么就不能偏向了
         * 因为加了hashcode之后对象头中存储了hashcode值,就无法存储线程id,所以jvm就设置其不可偏向
         */
        System.out.println("计算hashcode之前 ");
        String sb = ClassLayout.parseInstance(header).toPrintable();
        System.out.println(sb);

        System.out.println("计算hashcode之后 ");
        System.out.println(Integer.toHexString(header.hashCode()));
        System.out.println(Integer.toBinaryString(header.hashCode()));
        String s = ClassLayout.parseInstance(header).toPrintable();
        System.out.println(s);
    }
}
/**
计算hashcode之前 
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.example.thread.Header object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

计算hashcode之后 
768debd
111011010001101111010111101
com.example.thread.Header object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 bd de 68 (00000001 10111101 11011110 01101000) (1759427841)
      4     4        (object header)                           07 00 00 00 (00000111 00000000 00000000 00000000) (7)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*/

                                                                                                                                                          图4

对照参考图3和图4对前56位的对应解释分析上面代码的输出结果:

1.参照图3红线位置:unused,上面代码输出前25位都是00000000 00000000 00000000 0 。

2.计算hashcode之后打印出的16进制为7 68 de bd刚好是value的 bd de 68 de bd的逆序。这就是为什么要了解大小端存储的原因。

3.参照图3黄线位置: hashcode,分析上面代码计算hashcode之前这31位都是0,计算hashcode之后31位为768debd。

4.参照图3绿线位置: age,分析上面代码的4位都是 0000,说明对象在jvm中未进行幸存区的复制操作(jvm知识),由此也可以看出,对象在幸存区的复制次数最大可设置为15,因为二进制4位最大表示为15。

5.参照图3白线位置:biased(是否可偏向),分析上面代码位置,计算hashcode之前是 1(可偏向),计算hashcode之后0(不可偏向)。这也就是说对象在计算了hashcode之后不会是偏向锁。

6.参照图3蓝色位置:lock(偏向标识),分析上面代码位置,计算hashcode前后都是01 表示偏向锁。(注:01-偏向锁 00-轻量级锁 10-重量级锁 00-gc)

三、偏向锁、轻量级锁、重量级锁代码演示

3.1 偏向锁

如2.3中代码所示

3.2 轻量级锁

public class TestDemo {
    static Header header = new Header();

    public static void main(String[] args) {
        String s = ClassLayout.parseInstance(header).toPrintable();
        System.out.println(Integer.toHexString(header.hashCode()));
        System.out.println(s);

        synchronized (header){
            /**
             * 000 0-不可以偏向 00-轻量级锁
             */
            String s1 = ClassLayout.parseInstance(header).toPrintable();
            System.out.println(s1);
        }
    }
}
/**
--------------------------------------------------
输出结果
--------------------------------------------------
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
768debd
com.example.thread.Header object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

com.example.thread.Header object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           88 59 7b 07 (10001000 01011001 01111011 00000111) (125524360)
      4     4        (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*/

输出结果分析:查看计算hashcode值之后的偏向标识位00-轻量级锁。

3.3 重量级锁

public class TestDemo {
    private static Logger logger = LoggerFactory.getLogger(TestDemo.class);

    static Header header = new Header();

    static Thread t1,t2;
    public static void main(String[] args) throws InterruptedException {

        t1 = new Thread(() -> testLock());
        t2 = new Thread(() -> testLock());
        t1.setName("thread1");
        t2.setName("thread2");
        t1.start();
//        t1.join();
        t2.start();
    }
    public static void testLock(){
        synchronized (header){
            logger.info("name :" + Thread.currentThread().getName());
            logger.info(ClassLayout.parseInstance(header).toPrintable());
        }
    }
}
/**
--------------------------------------------------
输出结果
--------------------------------------------------
17:28:19.068 [thread1] INFO com.example.thread.TestDemo - name :thread1
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
17:28:19.844 [thread1] INFO com.example.thread.TestDemo - com.example.thread.Header object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           ba 08 03 12 (10111010 00001000 00000011 00010010) (302188730)
      4     4        (object header)                           ea 7f 00 00 (11101010 01111111 00000000 00000000) (32746)
      8     4        (object header)                           85 f0 00 f8 (10000101 11110000 00000000 11111000) (-134156155)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

17:28:19.844 [thread2] INFO com.example.thread.TestDemo - name :thread2
17:28:19.846 [thread2] INFO com.example.thread.TestDemo - com.example.thread.Header object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           ba 08 03 12 (10111010 00001000 00000011 00010010) (302188730)
      4     4        (object header)                           ea 7f 00 00 (11101010 01111111 00000000 00000000) (32746)
      8     4        (object header)                           85 f0 00 f8 (10000101 11110000 00000000 11111000) (-134156155)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*/

代码分析:可以看到偏向标识位为 10-重量级锁。因为线程1和线程2发生了资源竞争,所以对象锁转变为了重量级锁。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

半路笙歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值