张治峰的博客

synchronized锁状态及性能对比

2020-04-01

锁状态

概述

  • 无锁
    没有synchronized关键字修饰
  • 偏向锁
    JVM开启偏向锁时只有单个线程调用同步代码(不存在竞争条件)且无法重新偏向(单个锁情况,不考虑批量重偏向^[t1线程实例化同一个类多个对象并加锁,t2线程也对其加锁 由于锁升级会进行多次锁撤销操作,JVM会认为接下来的对象需要批量重偏向而不是进行锁升级为轻量级]
  • 轻量级锁
    多个线程调用同步代码,但是线程之间交替执行例如调用join().-自旋锁也是产生轻量级锁的一个方式
  • 重量级锁
    多个线程调用同步代码,且存在锁竞争

验证

对象头介绍

锁状态的标记就在对象头当中。网上32位操作系统对象头的文档很多,下面使用的是64位操作系统介绍

对象头的组成-mark Word

这部分主要用来存储对象自身的运行时数据,如hashcode、gc分代年龄等,32位操作系统和64位操作系统的位长度是不一样的,具体看图:

                             32位操作系统
|------------------------------------------------|-------------------| 
|                  Mark Word (32 bits)           |       State       |
|------------------------------------------------|-------------------|
| identity_hashcode:25|age:4|biased_lock:1|lock:2|       Normal      |
|------------------------------------------------|-------------------|
| thread:23 | epoch:2|age:4|biased_lock:1 |lock:2|       Biased      |
|------------------------------------------------|-------------------|
|           ptr_to_lock_record:30         |lock:2|Lightweight Locked |
|------------------------------------------------|-------------------|
|       ptr_to_heavyweight_monitor:30     |lock:2| Heavyweight Locked|
|------------------------------------------------|-------------------|
|                                         |lock:2|    Marked for GC  |
|------------------------------------------------|-------------------|

                                    64位操作系统   
|-------------------------------------------------------------------|-------------------|
|                  Mark Word (64 bits)                              |       State       |
|-------------------------------------------------------------------|-------------------|
| unused:25|identity_hashcode:31|unused:1|age:4|biased_lock:1|lock:2|       Normal      |
|------------------------------------------------------------|------|-------------------|
| thread:54|     epoch:2        |unused:1|age:4|biased_lock:1|lock:2|       Biased      |
|------------------------------------------------------------|------|-------------------|
|                           ptr_to_lock_record:62            |lock:2|Lightweight Locked |
|----------------------------------------------------------- |------|-------------------|
|                        ptr_to_heavyweight_monitor:62       |lock:2| Heavyweight Locked|
|------------------------------------------------------------|------|-------------------|
|                                                            |lock:2|    Marked for GC  |
|---------------------------------------------------------------------------------------|

JOL工具

JOL全称为Java Object Layout,是分析JVM中对象布局的工具,该工具大量使用了Unsafe、JVMTI来解码布局情况,分析结果是比较精准。使用时直接引入maven依赖即可

<!--java状态分析工具-->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

偏向锁代码

- JVM偏向锁延迟: JVM启动有大量的同步操作,偏向锁的锁消除性能很差所以JVM启动时会延迟偏向锁的启用。
- 对象进行hashcode以后 就不存在偏向锁 这和对象头的设计有关
- 偏向锁开启后新建对象 会有一个可偏向状态 但是指向的线程号为空 体现为二机制 0

/**
 * 由于在jvm启动时会延迟开启偏向锁 所以使用Thread.sleep(5000)
 * 设置JVM启动参数 -XX:BiasedLockingStartupDelay=0 延迟偏向锁时间0
 */
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        //睡眠5秒 使得偏向锁开启 也可以设置启动参数 
        //-XX:BiasedLockingStartupDelay=0 延迟偏向锁时间0
        Thread.sleep(5000);
        A a = new A();
        System.out.println("锁之前");
        System.out.println(ClassLayout.parseInstance(a).toPrintable());

        Thread thread = new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    System.out.println("锁状态");
                    System.out.println(ClassLayout.parseInstance(a).toPrintable());
                }
            }
        };
        thread.start();
        System.out.println("锁之后");
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}



结果
锁之前
com.example.concurrent.sync.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)                           a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160)
     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.concurrent.sync.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 d0 11 1b (00000101 11010000 00010001 00011011) (454152197)
      4     4        (object header)                           e6 7f 00 00 (11100110 01111111 00000000 00000000) (32742)
      8     4        (object header)                           a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160)
     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.concurrent.sync.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 d0 11 1b (00000101 11010000 00010001 00011011) (454152197)
      4     4        (object header)                           e6 7f 00 00 (11100110 01111111 00000000 00000000) (32742)
      8     4        (object header)                           a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160)
结果分别为 101+0 偏向锁状态 101+pid 偏向锁 101+pid偏向锁,后面两次打印结果中可以看到锁指向线程id是相同。 

轻量级锁

public class Demo2 {

    //JVM启动设置关闭偏向锁延迟
    public static void main(String[] args) throws InterruptedException {

        A a = new A();
        System.out.println("锁之前");
        System.out.println(ClassLayout.parseInstance(a).toPrintable());

        Thread thread = new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    System.out.println("线程锁状态");
                    System.out.println(ClassLayout.parseInstance(a).toPrintable());
                }
            }
        };

        /**
         * 主线程先拿锁 执行完成启动第二个线程 否则会产出重量级锁
         */
        synchronized (a){
            System.out.println("主线程锁状态");
            System.out.println(ClassLayout.parseInstance(a).toPrintable());
        }

        thread.start();

        System.out.println("锁之后");
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

结果
锁之前
com.example.concurrent.sync.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)                           05 c4 00 f8 (00000101 11000100 00000000 11111000) (-134167547)
     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.concurrent.sync.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 18 00 7a (00000101 00011000 00000000 01111010) (2046826501)
      4     4        (object header)                           8d 7f 00 00 (10001101 01111111 00000000 00000000) (32653)
      8     4        (object header)                           05 c4 00 f8 (00000101 11000100 00000000 11111000) (-134167547)
     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.concurrent.sync.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           f8 c8 12 0f (11111000 11001000 00010010 00001111) (252889336)
      4     4        (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      8     4        (object header)                           05 c4 00 f8 (00000101 11000100 00000000 11111000) (-134167547)
     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.concurrent.sync.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           f8 c8 12 0f (11111000 11001000 00010010 00001111) (252889336)
      4     4        (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      8     4        (object header)                           05 c4 00 f8 (00000101 11000100 00000000 11111000) (-134167547)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
结果分别为 101+0 偏向锁状态 101+pid 偏向锁 000 轻量级锁 000 轻量级锁

重量级锁

/**
 * 代码与轻量级锁的代码区别为thread.start()方法调用放到主线程加锁之前 使多个线程之间存在锁竞争
 *(自选状态之外)升级重量级锁
 */
 public class Demo3 {

     //JVM启动设置关闭偏向锁延迟
     public static void main(String[] args) throws InterruptedException {

         A a = new A();
         System.out.println("锁之前");
         System.out.println(ClassLayout.parseInstance(a).toPrintable());

         Thread thread = new Thread(){
             @Override
             public void run() {
                 synchronized (a){
                     System.out.println("线程锁状态");
                     System.out.println(ClassLayout.parseInstance(a).toPrintable());
                 }
             }
         };

         thread.start();
         /**
          * 主线程先拿锁 执行完成启动第二个线程 否则会产出重量级锁
          */
         synchronized (a){
             System.out.println("主线程锁状态");
             System.out.println(ClassLayout.parseInstance(a).toPrintable());
         }



         System.out.println("锁之后");
         System.out.println(ClassLayout.parseInstance(a).toPrintable());
     }
 }
结果:
锁之前
com.example.concurrent.sync.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)                           05 c4 00 f8 (00000101 11000100 00000000 11111000) (-134167547)
     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.concurrent.sync.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           5a 57 00 50 (01011010 01010111 00000000 01010000) (1342199642)
      4     4        (object header)                           a8 7f 00 00 (10101000 01111111 00000000 00000000) (32680)
      8     4        (object header)                           05 c4 00 f8 (00000101 11000100 00000000 11111000) (-134167547)
     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.concurrent.sync.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           5a 57 00 50 (01011010 01010111 00000000 01010000) (1342199642)
      4     4        (object header)                           a8 7f 00 00 (10101000 01111111 00000000 00000000) (32680)
      8     4        (object header)                           05 c4 00 f8 (00000101 11000100 00000000 11111000) (-134167547)
     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.concurrent.sync.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           5a 57 00 50 (01011010 01010111 00000000 01010000) (1342199642)
      4     4        (object header)                           a8 7f 00 00 (10101000 01111111 00000000 00000000) (32680)
      8     4        (object header)                           05 c4 00 f8 (00000101 11000100 00000000 11111000) (-134167547)
     12     4        (loss due to the next object alignment)

 结果分别为001 无锁 010 重量级锁 010重量级锁 010 重量级锁 (锁只会进行升级和锁释放,不会降级)

性能对比

偏向锁

//JVM启动设置关闭偏向锁延迟 -XX:BiasedLockingStartupDelay=0
public class Demo4 {
    public Long max = 1000000000L;

    public static void main(String[] args) throws InterruptedException {
        Demo4 demo4 = new Demo4();
        long start = System.currentTimeMillis();
        while (demo4.max>0){
            demo4.run1();
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end -start));
    }
    public synchronized void run1(){

       max--;
    }
}
执行结果 4294ms

轻量级锁

代码同上 将偏向锁延迟开启即可
结果  19682ms

重量级锁

 public class Demo5 {
     public CountDownLatch countDownLatch = new CountDownLatch(1000000000);
     public static void main(String[] args) throws InterruptedException {
         Demo5 demo4 = new Demo5();
         long start = System.currentTimeMillis();
         for (int i=0;i<2;i++){
             Thread thread = new Thread(){
                 @Override
                 public void run() {
                     while (demo4.countDownLatch.getCount()>0){
                         demo4.run1();
                     }
                 };
             };
             thread.start();
         }
         demo4.countDownLatch.await();
         long end = System.currentTimeMillis();
         System.out.println(String.format("%sms", end -start));
     }
     public synchronized void run1(){

         countDownLatch.countDown();
     }
 }
结果:44597ms

对象头的介绍在下次具体介绍推荐直接到oracle官网查看介绍

Tags: java
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章