并发中锁原理

 

锁的一切从对象头开始说起上面是64jdk源码里的一段注释。

翻译下:

 

 

 

意思是java的对象头在对象的不同状态下会有不同的表现形式,主要有三种状态,无锁状态、加锁状态、gc标记状态(暂时忽略)。那么可以理解java当中的取锁其实可以理解是给对象上锁,也就是改变对象头的状态,如果上锁成功则进入同步代 码块。但是java当中的锁有分为很多种,从上图可以看出大体分为偏向锁、轻量锁、重量锁三种锁状态。

 

java对象的布局以及对象头的布局

首先添加JOL的依赖

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

一、无锁例子

整个对象的大小一共16B,其中对象头(Object header)12B,int字段a(对象的实例数据)占 4B、这里其实还有对齐子节,因为现在正好是8的倍数,所以剩下的0B就是对齐字节不需要了。由此我们可以认为一个对象的布局大体分为三个部分分别是对象头(Object header)对象的实例数据字节对齐

引用openjdk文档当中对对象头的解释

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

上述引用中提到一个java对象头包含了2个word,并且好包含了堆对象的布局、类型、GC状态、同步状态和标识哈 希码,具体怎么包含的呢?又是哪两个word呢? 

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为第一个word根据文档可以知他里面包含了锁的信息,hashcode,gc信息等等,第二个word是什么 呢?

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".

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

假设理解一个对象头主要上图两部分组成(数组对象除外,数组对象的对象头还包含一个数组长度),那么 一个java的对象头多大呢?从JVM的源码注释中得知到一个mark word一个是64bit,那么klass的长度是多少呢? 所以需要想办法来获得java对象头的详细信息,验证一下他的大小,验证一下里面包含的信息是否正确。 根据上述利用JOL打印的对象头信息可以知道一个对象头是12B,其中8B是mark word 那么剩下的4B就是klass word了,和锁相关的就是mark word了,那么接下来重点分析mark word里面信息 在无锁的情况下markword当中的前56bit存的是对象的hashcode,那么来验证一下

 

 

没有进行hashcode之前的对象头信息,可以看到1-7B的56bit没有值,打印完hashcode之后就有值了,其中jvm-----是我们通过hashcode方法打印的结果,util------是根据1-7B的信息计算出来的 hashcode,所以可以确定java对象头当中的mark work里面的后七个字节存储的是hashcode信息,那么第一个字节当中的八位分别存的 就是分带年龄、偏向锁信息,和对象状态,这个8bit分别表示的信息如下图(其实上图也有信息),这个图会随着对象状态改变而改变, 下图是无锁状态下

关于对象状态一共分为五种状态,分别是无锁、偏向锁、轻量锁、重量锁、GC标记,那么2bit,如何能表示五种状 态(2bit最多只能表示4中状态分别是:00,01,10,11),jvm做的比较好的是把偏向锁和无锁状态表示为同一个状态,然 后根据图中偏向锁的标识再去标识是无锁还是偏向锁状态。

二、偏向锁例子

上面这个程序只有一个线程去调用sync方法,故而讲道理应该是偏向锁,但是发现输出的结果(第一个字节)依 然是00000001和无锁的时候一模一样,其实这是因为虚拟机在启动的时候对于偏向锁有延迟,比如把上述代码当中的睡眠注释掉结果就会不一样了,结果会变成00000101当然为了方便测试我们可以直接通过JVM的参数来禁用延迟-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0(除了这8bit,其他bit存储 了线程信息和epoch,这里不截图了),需要注意的after lock,退出同步后依然保持了偏向信息

jvm为什么会有延迟偏向锁这个设定呢?

个人理解,jvm启动时,自己本身有大量syn{}代码逻辑,各个线程争抢资源,比如gc. 而偏向锁升级为轻量级锁是一件很麻烦,很耗性能的事。

jvm启动自己知道不适合偏向锁,不如干脆启动的那几秒直接不用。所以出现上面睡眠后才出现偏向锁。执行应用代码时,jvm自己的启动代码已经执行完了。

 

三、轻量级锁

还是上面的图,没有设置禁止延时偏向锁,sync()内打印对象头状态00,对象由无锁进入轻量级锁

设置禁止延时偏向锁,sync()内打印对象头状态01,对象由偏向锁保持偏向锁

两个例子总结下就是:对象初始化后,根据当前运行条件(直接设置禁止延迟偏向锁、此时已经过了5秒偏向锁生效、此时还没有过5秒没有偏向锁)决定当前是无锁还是偏向锁状态。

如果是无锁,进入同步代码块则为轻量级锁。

如果是偏向锁,进入同步代码块还是为偏向锁。

轻量级锁使用场景为单线程的同步代码块、多线程的交替执行(不争抢执行权)

 

四、重量级锁

public class JOLExample3 {
    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 {
                        Thread.sleep(5000);
                        System.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());//无锁,分代标识
    }

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

}
befre 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    int A.a                                       11
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

t1 lock ing
A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           98 f7 65 1b (10011000 11110111 01100101 00011011) (459667352)
      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    int A.a                                       11
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

t1 release
t1 main lock
A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           4a d4 10 03 (01001010 11010100 00010000 00000011) (51434570)
      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    int A.a                                       11
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

after lock
A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           4a d4 10 03 (01001010 11010100 00010000 00000011) (51434570)
      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    int A.a                                       11
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

after gc()
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    int A.a                                       11
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total


Process finished with exit code 0

可以看到重量级锁退出同步代码块,对象还是重量级锁状态。直到gc后变成无锁,分代年龄+1.

        (00001001 00000000 00000000 00000000)

 

最后两条注意点:

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

public class JOLExample5 {
    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() {
                try {
                    synchronized (a) {
                        System.out.println("before wait");
                        out.println(ClassLayout.parseInstance(a).toPrintable());
                        a.wait();
                        System.out.println(" after wait");
                        out.println(ClassLayout.parseInstance(a).toPrintable());
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };


        t1.start();
        Thread.sleep(5000);
        synchronized (a) {
            a.notifyAll();
            out.println("notifyAll");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }

    }


}
befre 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    int A.a                                       11
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

before wait
A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           90 f6 23 1b (10010000 11110110 00100011 00011011) (455341712)
      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    int A.a                                       11
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

notifyAll
A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           8a 33 3e 18 (10001010 00110011 00111110 00011000) (406729610)
      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    int A.a                                       11
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

 after wait
A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           8a 33 3e 18 (10001010 00110011 00111110 00011000) (406729610)
      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    int A.a                                       11
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total


Process finished with exit code 0

 

如果对象已经计算了hashcode就不能偏向了

使用-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

public class JOLExample6 {
    static A a;

    public static void main(String[] args) throws Exception {
        Thread.sleep(5000);
        a = new A();
        a.hashCode();
        out.println("befre lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}
befre lock
A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 a5 e5 4a (00000001 10100101 11100101 01001010) (1256563969)
      4     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      8     4        (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
     12     4    int A.a                                       11
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值