从对象头浅谈偏向锁、轻量级锁、重量级锁

1.三种锁的简介
内置锁是JVM提供的最便捷的线程同步工具,在代码块或方法声明上添加synchronized关键字即可使用内置锁。对其优化使的juc包中的各种类性能提高。

重量级锁:内置锁在Java中被抽象为监视器锁(monitor)。在JDK 1.6之前,监视器锁可以认为直接对应底层操作系统中的互斥量(mutex)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁”。内核态与用户态的切换上不容易优化,但通过自旋锁,可以减少线程阻塞造成的线程切换

轻量级锁:减少无实际竞争情况下,使用重量级锁产生的性能消耗,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。轻量级锁是相对于重量级锁而言的。使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁。

偏向锁:轻量级锁每次申请、释放锁都至少需要一次CAS,但偏向锁只有初始化时需要一次CAS。“偏向”的意思是,偏向锁假定将来只有第一个申请锁的线程会使用锁(不会有任何线程再来申请锁),因此,只需要在Mark Word中CAS记录owner(本质上也是更新,但初始值为空),如果记录成功,则偏向锁获取成功,记录锁状态为偏向锁,以后当前线程等于owner就可以零成本的直接获得锁;否则,说明有其他线程竞争,膨胀为轻量级锁。

偏向锁、轻量级锁、重量级锁适用于不同的并发场景:

偏向锁:无实际竞争,且将来只有第一个申请锁的线程会使用锁。
轻量级锁:无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。
重量级锁:有实际竞争,且锁竞争时间长。
2.锁的底层原理
线程的开启底层实现

new Thread(()-> System.out.println("How to begin run method ")).start();
private native void start0();
jvm 中会对应一个start0方法和OS函数交互,通过jni技术回调run方法。

在OS控制台输入指令

man pthread_create

加锁的底层实现
synchronized (this){ System.out.println(“get lock”);
man pthread_mutex_init 该os函数会初始化一锁对象

man pthread_mutex_lock 该os函数会将锁对象进行锁定

3,浅析对象头
从openjdk文档中获取对象头的定义 http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

从对象头浅谈偏向锁、轻量级锁、重量级锁_第1张图片

OOP-Klass二分模型 可参考https://blog.csdn.net/jyxmust/article/details/88255594 简单了解下

JVM中对象定义的注释

// 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)

从对象头浅谈偏向锁、轻量级锁、重量级锁_第2张图片

4.从具体代码来看对象头信息
为了能够查看jvm中对象头的布局信息,openjdk提供了jol工具进行对象的查看,http://openjdk.java.net/projects/code-tools/jol/ 引入依赖即可

org.openjdk.jol jol-core 0.10 接着用代码进行演示分析 package com.passion;

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;

/**

  • @author: liupc
  • @description:
    **/
    public class Test {
    public static void main(String[] args) {
    A a = new A();
    out.println(ClassLayout.parseInstance(a).toPrintable());
    }
    }

class A{
private boolean flag = false;
private String name = null;
}
对象头的布局信息 展开源码
从对象头浅谈偏向锁、轻量级锁、重量级锁_第3张图片

对象的hashcode存储 展开源码

package com.passion;

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

import static java.lang.System.out;

/**

  • @author: liupc
  • @description:
    **/
    public class Test2 {
    public static void main(String[] args) {
    A a = new A();
    //jvm的信息
    out.println(VM.current().details());
    out.println(“jvm------------0x”+Integer.toHexString(a.hashCode()));
    out.println(ClassLayout.parseInstance(a).toPrintable());
    }
    }
    从对象头浅谈偏向锁、轻量级锁、重量级锁_第4张图片

由于这里是小端存储,所以打印出来的hashcode值和二进制位中是反向的,可以借助工具验证 https://tool.oschina.net/hexconvert/

标红的16进制是我们通过hashcode方法打印的结果,标红的1-7Bit位的信息是二进制 hashcode,所以可以确定java对象头当中的mark work里面的后七个字节存储的是hashcode信息,

那么第一个字节当中的八位分别存的 就是分带年龄、偏向锁信息,和对象状态,这个8bit分别表示的信息如下图(其实上图也有信息),这个图会随着对象状态改变而改变, 下图是无锁状态下

从对象头浅谈偏向锁、轻量级锁、重量级锁_第5张图片

关于对象状态一共分为五种状态,分别是无锁、偏向锁、轻量锁、重量锁、GC标记。

5.代码演示三种锁
jvm启动会有默认偏向锁延迟。

轻量锁验证 展开源码

package com.passion;

import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;

/**

  • @author: liupc
  • @description: 默认会有偏向锁延迟
    **/
    public class TestLightWeightLock {
    public static void main(String[] args) {
    Object a = new Object();
    out.println(“befor lock”);
    out.println(ClassLayout.parseInstance(a).toPrintable()); //01 无锁
    synchronized (a){
    out.println(“lock ing”);
    out.println(ClassLayout.parseInstance(a).toPrintable()); //00 轻量锁
    }
    out.println(“after lock”);
    out.println(ClassLayout.parseInstance(a).toPrintable()); //无锁
    }
    }

从对象头浅谈偏向锁、轻量级锁、重量级锁_第6张图片

添加jvm启动参数关闭偏向锁延迟,或者线程睡眠4-5秒也可验证开启偏向锁

//关闭延迟开启偏向锁

-XX:BiasedLockingStartupDelay=0

//禁止偏向锁

-XX:-UseBiasedLocking

//启用偏向锁

-XX:+UseBiasedLocking

偏向锁验证 展开源码

package com.passion;
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
/**

  • @author: liupc
  • @description:
    **/
    public class TestBiasedLock {
    public static void main(String[] args) throws InterruptedException {
    // Thread.sleep(5000); -XX:BiasedLockingStartupDelay=0
    Object a = new Object();
    out.println(“befor lock”);
    out.println(ClassLayout.parseInstance(a).toPrintable()); //101 偏向锁
    synchronized (a) {
    out.println(“lock ing”);
    out.println(ClassLayout.parseInstance(a).toPrintable()); //101 偏向锁
    }
    out.println(“after lock”);
    out.println(ClassLayout.parseInstance(a).toPrintable()); //101 偏向锁
    }
    }
    从对象头浅谈偏向锁、轻量级锁、重量级锁_第7张图片

偏向锁解锁后还是偏向锁,但是可以看到对象头记录的hashcode没有改变。

验证重量锁的产生,当有多个线程争强锁资源时就会发生。

重量锁验证 展开源码
package com.passion;
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
/**

  • @author: liupc
  • @description:
    **/
    public class TestWightLock {
    static Object a = new Object();
    public static void main(String[] args) {
    out.println(“befor lock”);
    out.println(ClassLayout.parseInstance(a).toPrintable());
    for(int i = 0 ; i < 2 ; i++){
    new Thread(()->print()).start();
    }
    out.println(“after lock”);
    out.println(ClassLayout.parseInstance(a).toPrintable());
    }
    public static void print(){
    synchronized (a){
    System.out.println(“weight lock”);
    out.println(ClassLayout.parseInstance(a).toPrintable());
    }
    }
    }

从对象头浅谈偏向锁、轻量级锁、重量级锁_第8张图片

6.三种锁之间的性能对比

package com.passion;
import java.util.concurrent.CountDownLatch;

/**

  • @author: liupc

  • @description: 锁性能测试
    **/
    public class LockCompitition {

    static CountDownLatch c = new CountDownLatch(1000000000);

    public static void main(String[] args) throws InterruptedException {
    TestCount countTest = new TestCount();

     long start = System.currentTimeMillis();
     for (int i = 0; i < 2; i++) {
         new Thread(() -> {
             while (c.getCount() > 0) {
                 countTest.parse();
             }
         }).start();
     }
    

// for (int i = 0 ; i <1000000000 ; i++ ){
// countTest.parse();
// }
c.await();
long end = System.currentTimeMillis();

    System.out.println(end - start);
}

}
class TestCount {
int i = 0;

public synchronized void parse() {
    i++;
    LockCompitition.c.countDown();
}

}
锁性能测试 展开源码
偏向锁 9338ms

轻量锁 24913ms

重量锁 35838ms

7.锁分配和膨胀过程可以参考下图:

从对象头浅谈偏向锁、轻量级锁、重量级锁_第9张图片

你可能感兴趣的:(从对象头浅谈偏向锁、轻量级锁、重量级锁)