JVM synchronized锁实现原理,看完还不懂算我输!!!

877 阅读7分钟

本文将用代码带大家一探究竟,看看JVM究竟是如何实现线程同步以及锁的升级过程, jdk版本为HotSpot 64bit 1.8.0_91, Talking is cheap, show me the code!!!

一、synchronized关键字的实现

我们将一段java代码通过javap -c -s命令反编译一下看看

package com.ywzlp.demo;

/**
 * Created by yuwei on 2020/6/6
 */
public class TestMonitor {

    public static void main(String[] args) {
        Object lock = new Object();
        synchronized (lock) {
            System.out.println("hello lock");
        }
    }
}

/*
    首先执行javac TestMonitor.java编译
    再执行 javap -c -s TestMonitor.class进行反编译
    结果如下:
    public class com.ywzlp.demo.TestMonitor {
  public com.ywzlp.demo.TestMonitor();
    descriptor: ()V
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    Code:
       0: new           #2                  // class java/lang/Object
       3: dup
       4: invokespecial #1                  // Method java/lang/Object."<init>":()V
       7: astore_1
       8: aload_1
       9: dup
      10: astore_2
      11: monitorenter
      12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      15: ldc           #4                  // String hello lock
      17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      20: aload_2
      21: monitorexit
      22: goto          30
      25: astore_3
      26: aload_2
      27: monitorexit
      28: aload_3
      29: athrow
      30: return
    Exception table:
       from    to  target type
          12    22    25   any
          25    28    25   any
}
*/

我们可以看到,synchronized被编译后会生成两条指令,monitorentermonitorexit,下面带大家看看这两条指令具体会做些什么操作

二、java对象在内存中的存储布局

在讲锁实现之前,我们需要了解java对象的布局,可以使用open-jdk的jol包,全称Java object layout,可以看到java对象在内存中的布局,maven地址:

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

我们看看下面程序运行结果

package com.ywzlp.demo;

import org.openjdk.jol.info.ClassLayout;

/**
 * Created by yuwei on 2020/6/6
 */
public class TestLock {
    private int num;
    private String str;

    public static void main(String[] args) {
        TestLock testLock = new TestLock();
        System.out.println(ClassLayout.parseInstance(testLock).toPrintable());
    }
}
/**运行结果
 com.ywzlp.demo.TestLock 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 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4                int TestLock.num                              0
     16     4   java.lang.String TestLock.str                              null
     20     4                    (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
 * 
 */

对象布局包含下面几个部分

  1. markword(包含锁、GC、hashCode信息),占用8个字节
  2. klass pointer (类型指针),开启指针压缩并且JVM内存小于32G时占用4个字节,未开启指针压缩或者JVM内存大于32G占用8个字节
  3. 数组长度(数组对象才有),占用4个字节
  4. 成员变量数据(基本类型存的数据,对象类型存的引用指针)
  5. padding(对齐,如果对象大小不是8的整数倍将补齐)

三、synchronized锁升级过程

锁信息存放在对象的markword中,下面是markword的结构

|--------------------------------------------------------------------------------|--------------------|
|                                  Mark Word (64 bits)                           |       锁状态		  |
|--------------------------------------------------------------------------------|--------------------|
| unused:25 | identity_hashcode:31 | cms_free:1 | age:4 | biased_lock:1 | lock:2 |        无锁		  |
|--------------------------------------------------------------------------------|--------------------|
| thread:54 |       epoch:2        | cms_free:1 | age:4 | biased_lock:1 | lock:2 |       偏向锁		  |
|--------------------------------------------------------------------------------|--------------------|
|                         ptr_to_lock_record                            | lock:2 |		轻量级锁		  |
|--------------------------------------------------------------------------------|--------------------|
|                     ptr_to_heavyweight_monitor                        | lock:2 | 		重量级锁		  |
|--------------------------------------------------------------------------------|--------------------|

对象一共有五个状态

锁状态 markword标志位
无锁 001
偏向锁 101
轻量级锁(自旋锁) 00
重量级锁 10
标记GC 11

代码演示标示位变化

package com.ywzlp.demo;

import org.openjdk.jol.info.ClassLayout;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * -XX:BiasedLockingStartupDelay=0
 * Created by yuwei on 2020/6/6
 */
public class TestLock {
    private int num;
    private String str;

    public static void main(String[] args) throws InterruptedException {
        TestLock testLock = new TestLock();
        System.out.println("---------------无锁boundary-----------------");
        System.out.println(ClassLayout.parseClass(TestLock.class).toPrintable(testLock));
        System.out.println("---------------无锁boundary-----------------");

        System.out.println("---------------偏向锁boundary-----------------");
        testLock.add(500);
        System.out.println("---------------偏向锁boundary-----------------");

        System.out.println("---------------轻量级锁boundary-----------------");
        Thread t1 = new Thread(() -> testLock.add(500));
        t1.start();
        t1.join();
        System.out.println("---------------轻量级锁boundary-----------------");

        System.out.println("---------------重量级锁boundary-----------------");
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        List<Future<?>> futureList = new ArrayList<>(2);
        for (int i = 0; i < 2; i++) {
            futureList.add(executorService.submit(() -> testLock.add(500)));
        }
        futureList.forEach(future -> {
            try {
                future.get();
            } catch (Exception ignored) {}
        });
        executorService.shutdown();
        futureList.clear();
        System.out.println("---------------重量级锁boundary-----------------");



    }

    public synchronized void add(int timeToSleep) {
        if (timeToSleep > 0) {
            try {
                Thread.sleep(timeToSleep);
            } catch (InterruptedException ignored) {
            }
        }
        num++;
        System.out.println(ClassLayout.parseClass(TestLock.class).toPrintable(this));
    }
}

/*
    运行结果:
    ---------------无锁boundary-----------------
com.ywzlp.demo.TestLock 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 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4                int TestLock.num                              0
     16     4   java.lang.String TestLock.str                              null
     20     4                    (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

---------------无锁boundary-----------------
---------------偏向锁boundary-----------------
com.ywzlp.demo.TestLock object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 48 80 25 (00000101 01001000 10000000 00100101) (629164037)
      4     4                    (object header)                           fa 7f 00 00 (11111010 01111111 00000000 00000000) (32762)
      8     4                    (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4                int TestLock.num                              1
     16     4   java.lang.String TestLock.str                              null
     20     4                    (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

---------------偏向锁boundary-----------------
---------------轻量级锁boundary-----------------
com.ywzlp.demo.TestLock object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           f0 f7 4b 07 (11110000 11110111 01001011 00000111) (122419184)
      4     4                    (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      8     4                    (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4                int TestLock.num                              2
     16     4   java.lang.String TestLock.str                              null
     20     4                    (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

---------------轻量级锁boundary-----------------
---------------重量级锁boundary-----------------
com.ywzlp.demo.TestLock object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           3a b1 82 26 (00111010 10110001 10000010 00100110) (646099258)
      4     4                    (object header)                           fa 7f 00 00 (11111010 01111111 00000000 00000000) (32762)
      8     4                    (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4                int TestLock.num                              3
     16     4   java.lang.String TestLock.str                              null
     20     4                    (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

com.ywzlp.demo.TestLock object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           3a b1 82 26 (00111010 10110001 10000010 00100110) (646099258)
      4     4                    (object header)                           fa 7f 00 00 (11111010 01111111 00000000 00000000) (32762)
      8     4                    (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4                int TestLock.num                              4
     16     4   java.lang.String TestLock.str                              null
     20     4                    (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

---------------重量级锁boundary-----------------

*/

注意在运行时需要加上JVM参数-XX:BiasedLockingStartupDelay=0,这个参数代表启动时就开启偏向锁.JVM默认会延迟几秒钟开启偏向锁,是因为JVM在启动时会有多线程锁竞争,而在偏向锁升级到轻量级锁时会有一系列的复杂过程,所以在明知道会有竞争的情况下干脆不启用偏向锁。

我们可以看到markword第6-8位的打印如下:

101 -> 101 -> 000 -> 010 -> 010

为什么没有看到无锁的001状态? 因为一旦开启了偏向锁后,创建对象默认会是匿名偏向状态,此时markword的threadId为空,001状态大家可以在上一个程序中看到

转载请注明出处