并发编程----4、对象头详解

这一节非常重要,是整个并发锁的关键,因为重视提到无所、偏向锁、轻量锁、重量锁这些概念,很多人不知道这些东西是什么,为什么这样称呼,这一节好好看。

目录

一、概念

二、用代码探究原理

2.1、初探java对象头布局

2.2、对象头详解


一、概念

对象头到底是什么东西呢?我们从hotspot的源码中贴上一段描述。

 

//  32 bits:
//  --------
//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
//             size:32 ------------------------------------------>| (CMS free block)
//             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
//  64 bits:
//  --------
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
//  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
//  size:64 ----------------------------------------------------->| (CMS free block)

 

网上的那个锁的各种结构说明都是32位操作系统的,现在32位操作系统应该都已经见不到了吧。所以我们直接看64位操作系统,下面是64位操作系统的对象头的描述翻译如下。

 

|--------------------------------------------------------------------------------------------------------------|
|                                              Object Header (128 bits)                                        |
|--------------------------------------------------------------------------------------------------------------|
|                        Mark Word (64 bits)                                    |      Klass Word (64 bits)    |默认开启指针压缩(32bits)       
|--------------------------------------------------------------------------------------------------------------|
|unused:25|identity_hashcode:31(56) | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  无锁
|----------------------------------------------------------------------|--------|------------------------------|
|  thread:54 |         epoch:2      | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  偏向锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_lock_record:62                            | lock:2 |     OOP to metadata object   |  轻量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_heavyweight_monitor:62                    | lock:2 |     OOP to metadata object   |  重量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                                                                      | lock:2 |     OOP to metadata object   |    GC
|--------------------------------------------------------------------------------------------------------------|

从上图可以看到的是对象头分为 MarkWord 和 Klass Word 两部分。 KlassWord默认开启了指针压缩,实际占用空间不到64bits。没有开启指针压缩的时候是64bits。

Java的对象头在对象的不同状态下会有不同的表现形式,主要有三种状态:无锁状态、加锁状态、gc标记状态。那么我们可以理解java当中的取锁其实可以理解是给对象上锁,也就是改变对象头状态。如果上锁成功就可以进入同步代码块。

三种锁状态:偏向锁、轻量级锁、重量级锁的效率差距会让你惊讶,我们下面慢慢说。

其中

MarkWord 一般就是锁信息存储的地方,由上图看可以分成:无锁、偏向锁、轻量锁、重量锁、gc。

KlassWord 存的是指向元数据的指针,元数据就是类被JVM加载进内存中方法区的字节码,也可以理解为指向了class文件在jvm中存储的地址。

二、用代码探究原理

这块可是非常非常重要的,也是网上根本看不到的东西。我可是下苦功才弄明白的。我们先用JOL来看看对象头到底是什么样子,还好java提供了来分析java对象布局的工具JOL。利用maven引入:

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

2.1、初探java对象头布局

我们创建一个空的Class ,代码如下:

package com.hubin.lock;
public class A {
}

 

在建立一个文件打印出来对象头:

package com.hubin.lock;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import static java.lang.System.out;

public class JolExample {
    public static void main(String[] args) {
        A a = new A();
        out.println(VM.current().details());
       out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

 

 

运行结果:

# Using compressed oop with 0-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      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)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


Process finished with exit code 0

 

下面我们来分析下这输出都是什么意思

Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes] 对应的是:[Oop(Ordinary Object Pointer 普通对象指针4bytes),boolean,byte,char,short,int ,float,long,double] 。

对象头(object header)一共16Bytes。 其中有4B是对齐的字节(因为在64位虚拟机上对象的大小必

须是8的倍数),由于这个对象里面没有任何字段,故而对象的实例数据为0B。

如果我的A类里面在写几个属性值,再次运行结果如下:最后的对其字节就是用来对其boolean的。

 

public class A {
    boolean a;
    int b;
    float c;
}

 

com.hubin.lock.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      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)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4       int A.b                                       0
     16     4     float A.c                                       0.0
     20     1   boolean A.a                                       false
     21     3           (loss due to the next object alignment)
Instance size: 24 bytes

因为加的属性比较多,所以对象整体变成了24B了,但是对象头没有变还是12B,由此可以看出来对象布局大体由三部分构成,对象头、实例数据、字节对齐。

下面就仔细探讨下对象头12B的组成。

2.2对象头详解

对象头的官网说明可以看下面链接,http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

 

大概意思就是java对象头包含对象布局、类型、GC状态、同步状态和标识hash码。由2个word组成。

 

mark word 为第一个word根据文档可以得知它里面包含了锁的信息,hashcode,gc信息等等。

 

Klass为对象头的第二个word,主要指向对象的元数据。

 

假设我们理解一个对象头主要上图两部分组成(数组对象除外,数组对象的对象头还包含一个数组长度),一个java对象头多大呢?JVM源码写的很清楚,64bit。但是没有写Klass长度多少。从我们上面利用JOL打印的结果就可以看出来一个对象头是12Bytes。其中8Bytes是MarkWord,那么剩余的就是4Bytes就是Klass Word了,但是是不是最开始的图里面写的是64Bits也就是8Bytes,大家会问为啥不一样呢?因为我开始也说了一句,就是指针压缩,默认开启压缩,如果不压缩就是8Bytes了。和所相关的都在MarkWord里面所以我们着重分析下MarkWord。

 

1、无锁状态

 

无锁状态的MarkWord的每个bit的说明,前25个bit没用,也就是可以说前56个bits存的是hashcode,age占4个bits(gc标记用的,这个也就是说gc最大标记也就是15次),biased_lock表示是否是偏向锁,lock用2位来表示锁状态。

unused:25|identity_hashcode:31(56) | unused:1 | age:4 | biased_lock:1 | lock:2

但是我们看到之前的输出hashcode都是0,这是为啥呢。我们来一探究竟。

我们运行一个计算hashcode的代码示例:

package com.hubin.lock;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import static java.lang.System.out;
public class JolExample2 {
    public static void main(String[] args) {
        A a = new A();
        out.println("before hash:");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        out.println("jvm----hash code:  "+Integer.toHexString(a.hashCode()));
       //HashUtil.countHash(a); 打印对象头里面的转换成十六进制会发现跟上一句输出一样。
       out.println("after hashcode:");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

我们中间输出下hashcode,这样就相当于做了一次hashcode计算。然后再看对象头信息就变了。原来都是0变成了有1出险了,利用二进制转化成十六进制来看,跟计算的a的hashcode是一样的。但是为什么是反的呢,因为window的操作系统是小端存储,小端存储就是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。

所以最开始的8位就是unused:1 | age:4 | biased_lock:1 | lock:2

 

 

然后依次是hashcode和unused。因为低位存低地址。所以大家用进制转换工具(https://tool.oschina.net/hexconvert/)就可以看到转换出来的十六进制跟打印出来的是一致的。

 

before hash:
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      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)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

jvm----hash code:  6ae40994
after hashcode:
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 94 09 e4 (00000001 10010100 00001001 11100100) (-469134335)
      4     4        (object header)                           6a 00 00 00 (01101010 00000000 00000000 00000000) (106)
      8     4        (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 by看tes internal + 4 bytes external = 4 bytes total

当然可以用一个进制转换工具类输出也是可以的。我给出代码:

package com.hubin.lock;
import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class HashUtil {
    public static void countHash(Object object) throws NoSuchFieldException, IllegalAccessException {
        // 手动计算HashCode
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);
        long hashCode = 0;
        for (long index = 7; index > 0; index--) {
            // 取Mark Word中的每一个Byte进行计算
            hashCode |= (unsafe.getByte(object, index) & 0xFF) << ((index - 1) * 8);
        }
        String code = Long.toHexString(hashCode);
        System.out.println("util-----------0x"+code);
    }
}

 

看输出可以看出来,1-9行 before hash的输出的对象头,1-7Bytes都是0,证明hashcode是没有的。打印完后再看12到19行,after hash的时候就出现值了。可以证明hashcode如果不主动计算,是不会自动生成的。

对象一共五种状态:无锁,偏向锁、轻量锁、重量锁、gc标记,但是锁状态只有2位最多只能表示4中状态,所以JVM将无锁和偏向锁用相同的lock状态表示,但是用biased_lock来区分。

 

2、偏向锁

看下面代码:

package com.hubin.lock;
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JolExample3 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        A a = new A();
        out.println("before lock:");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        synchronized (a){
            out.println("locking......");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
        out.println("after lock:");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}
before lock:
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      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)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
locking......
after lock:
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      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)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     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 方法,按理说应该a的对象头里面的锁状态应该变成了偏向锁应该是00000101才对。但是最后结果还是00000001无锁。是不是错了?其实没错,这是因为虚拟机在启动的时候对于偏向锁有延迟。比如我们在代码里面加个睡眠5s(延迟加载默认是4s),结果就是偏向锁了。当然我们还可以关掉延迟通过以下参数可以禁止延迟:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 我们加上这个参数试下结果跟我们说的一致。

 

before lock:
com.hubin.lock.A 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 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

locking......
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 e0 c7 02 (00000101 11100000 11000111 00000010) (46653445)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

after lock:
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 e0 c7 02 (00000101 11100000 11000111 00000010) (46653445)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes

 

为什么偏向锁要设置延迟呢?因为JVM认为很多资源都会存在竞争关系,也就是很多代码块都会有synchronized,一个线程访问可以使偏向锁,但是多个线程访问就会升级成轻量级锁了,锁升级先要消除偏向锁,这个是非常麻烦的过程,所以jvm一般先延迟4s在启动偏向锁。偏向锁的MarkWord如下: thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2

偏向锁没有hashcode,这个说明hashcode和偏向锁无法共存。

大家肯定有个疑问? 为什么before lock 也是偏向锁,after lock也是呢?

after lock退出同步后,依然保持了偏向信息。因为偏向锁就是偏向同一个线程。 这个我们在下一章节锁膨胀在细说。

但是有个问题要注意,如果是如下的代码:

 A a = new A();
Thread.sleep(5000);

这样模拟不出来延迟偏向的结果,对象a还是不可偏向的,因为a对象在睡眠之前已经被初始化了。如果将Sleep放到初始化前面的话就是可偏向的了。

 

 

3、轻量级锁

轻量锁一定是无锁状态通过CAS才能膨胀成轻量锁。如果开始是偏向锁,也会先撤销成无锁然后再CAS变成轻量锁。

 

package com.hubin.lock;
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JolExample5 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        A a = new A();
        out.println("before lock:");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        synchronized (a){
            out.println("locking......");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
        out.println("after lock:");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}
before lock:
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      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)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

locking......
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           48 f4 39 03 (01001000 11110100 00111001 00000011) (54129736)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

after lock:
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      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)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

输出结果可以看到在locking中,打印的对象头中,a对象的锁状态是轻量级锁00。轻量级锁尝试在应用层面解决线程同步问题,而不触发操作系统的互斥操作,轻量级锁减少多线程进入互斥的几率,不能代替互斥。

同步分为三种情况:

1、单线程(单线程也可以加同步块)

2、多线程交替执行,无竞争(或者自旋能够获取到锁) 一般自旋时间很短就是一个线程上下文的切换时间。 这个时候就会变成轻量级锁

3、多线程互斥执行

轻量级锁在退出同步块,后会恢复成为无锁状态。

 

4、重量级锁

 

老样子看下重量级锁的表头,这里就干脆多打印点状态。重量级锁需要去竞争资源,第一次同步等待5s,这期间再去获取对象a锁,则a就会升级成重量级锁。

重量级锁在退出同步后也是保持重量级锁状态。

package com.hubin.lock;
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JOLExample7 {
    static A a;
    public static void main(String[] args) throws Exception {
        a = new A();
        out.println("befre lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());//无锁
        Thread t1= new Thread(){
            public void run() {
                synchronized (a){
                    try {
                        Thread.sleep(5000);
                        out.println("t1 release");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
        Thread.sleep(1000);
        out.println("t1 lock ing");
        out.println(ClassLayout.parseInstance(a).toPrintable());//轻量锁
        sync(); //竞争变重量级
        out.println("after lock");
        //重量级锁,因为锁状态没改变回来
        out.println(ClassLayout.parseInstance(a).toPrintable());
        System.gc();
        out.println("after gc()");
        out.println(ClassLayout.parseInstance(a).toPrintable());//无锁---gc
    }

    public  static  void sync() throws InterruptedException {
        synchronized (a){
            out.println("t1 main lock");
            out.println(ClassLayout.parseInstance(a).toPrintable());//重量锁
        }
    }
}

看看各个情况下对象头: 10是重量级锁状态

befre lock
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      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)                           9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
     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 lock ing
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           68 f6 ec 1b (01101000 11110110 11101100 00011011) (468514408)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
     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 release
t1 main lock
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           aa 1c 36 18 (10101010 00011100 00110110 00011000) (406199466)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

after lock
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           aa 1c 36 18 (10101010 00011100 00110110 00011000) (406199466)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

after gc()
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           09 00 00 00 (00001001 00000000 00000000 00000000) (9)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 

5、性能对比 偏向锁 VS 轻量级锁 VS 重量级锁

我们在B中加入一个计数器,并且用下面配置控制是否开启偏向

‐XX:BiasedLockingStartupDelay=20000

‐XX:BiasedLockingStartupDelay=0

package com.hubin.lock;
public class B {
    int i;
    public synchronized void add(){
        i++;
    }
}
package com.hubin.lock;
public class JOLExample4 {
    public static void main(String[] args) throws Exception {
        B b = new B();
        long start = System.currentTimeMillis();
        //调用同步方法1000000000L 来计算1000000000L的++,对比偏向锁和轻量级锁的性能
        for (int i = 0; i < 1000000000L; i++) {
            b.add();
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end - start));
    }
}

 

重量级锁需要模拟2个线程来竞争资源,我们利用CountDownLatch来模拟

package com.hubin.lock;
public class C {
    int i;
    public synchronized void add(){
        i++;
        JOLExample6.countDownLatch.countDown();
    }
}
package com.hubin.lock;
import java.util.concurrent.CountDownLatch;
public class JOLExample6 {
    static CountDownLatch countDownLatch = new CountDownLatch(1000000000);
    public static void main(String[] args) throws Exception {
        final C c = new C();
        long start = System.currentTimeMillis();
        //调用同步方法1000000000L 来计算1000000000L的++,对比偏向锁和轻量级锁的性能
        for(int i=0;i<2;i++){
            new Thread(){
                @Override
                public void run() {
                    while (countDownLatch.getCount() > 0) {
                        c.add();
                    }
                }
            }.start();
        }
        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end - start));
    }
}

 

偏向锁运行结果是: 1607ms

轻量级锁运行结果是:18598ms

重量级锁运行结果是:37371ms

差距太明显了。

6、 调用wait方法则立马变成重量级锁

看如下代码

package com.hubin.lock;
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JOLExample8 {
    static A a;
    public static void main(String[] args) throws Exception {
        a = new A();
        out.println("befre lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        Thread t1= new Thread(){
            public void run() {
                synchronized (a){
                    try {
                       out.println("before wait");
                        out.println(ClassLayout.parseInstance(a).toPrintable());
                        a.wait(); //阻塞,让出锁资源
                        out.println("after wait");
                        out.println(ClassLayout.parseInstance(a).toPrintable());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
        Thread.sleep(5000);
        synchronized (a){
            //唤醒
            a.notifyAll();
        }
    }
}

结果:

befre lock
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      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)                           9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

before wait
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           c8 f5 6b 1b (11001000 11110101 01101011 00011011) (460060104)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

after wait
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           aa 32 bf 17 (10101010 00110010 10111111 00010111) (398406314)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 调用wait会立马释放锁,而调用notify的话不会立马释放锁,还是会执行完后续代码才释放锁。有个朋友正好遇到类似得一个面试题,下面来看个面试题:大家看看下面代码会有什么问题。

解析:notify方法只能唤醒一个正在等待 这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒一个,但是调用notify这个方法不会马上释放锁,必须等到Synchronized方法或者语法块执行完才真正释放。所以上面代码会造成死锁。

 

7、计算了hashcode就无法成为偏向锁了

看个例子

import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
public class JOLExample9 {
    static A a;
    public static void main(String[] args) throws Exception {
        //-XX:BiasedLockingStartupDelay=0
        a = new A();
        out.println("befre hash");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        a.hashCode();
        synchronized (a) {
            out.println("after hash");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
    }
}

取消偏向锁延迟的输出结果: hash值计算完后,就是轻量级锁了。如果不计算hashcode,就是偏向锁。

 

befre hash
com.hubin.lock.A 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 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

after hash
com.hubin.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           b0 f4 11 03 (10110000 11110100 00010001 00000011) (51508400)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes intern al + 4 bytes external = 4 bytes total

下面就是对象头得知识,看完这个大家应该对无所,偏向锁,轻量锁,重量锁,了如执掌了吧。下一级分析锁膨胀。 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值