分析 Synchronized 锁的4个状态

1.前言

通过JOL查看对象的头信息,Synchronized 关键字锁的状态:无锁、偏向锁、轻量级锁、重量级锁

  • 基于64位 jdk8测试

int pthread_mutex_lock(pthread_mutex_t *mutex);

pthread_mutex_lock()返回时,该互斥锁已被锁定。线程调用该函数让互斥锁上锁,如果该互斥锁已被另一个线程锁定和拥有,则调用该线程将阻塞,直到该互斥锁变为可用为止。 对于 Solaris线程,请参见mutex_lock 语法

  • 会阻塞


我们可以从JVM 源码中看到对glibc库调用,操作系统层级的锁

ThreadCritical::ThreadCritical

1
2
3
4
5
6
7
8
9
10
ThreadCritical::ThreadCritical() {
pthread_t self = pthread_self();
if (self != tc_owner) {
int ret = pthread_mutex_lock(&tc_mutex);
guarantee(ret == 0, "fatal error with pthread_mutex_lock()");
assert(tc_count == 0, "Lock acquired with illegal reentry count.");
tc_owner = self;
}
tc_count++;
}

这个是底层系统库,我们是看不到源码的,只能自己下载。可以在glibc/nptl/phread_mutex_lock.c里看到实现。

2.验证准备

  • 先通过一个jni调用来获取pthread层面的tid

    JniTool.java

    1
    2
    3
    4
    5
    6
    7
    8
    package jnitool;

    public class JniTool {
    static {
    System.loadLibrary("jnitools");
    }
    public native void getThreadID();
    }
    这里jnitool必须在 java.library.path这一jvm变量所指向的路径中,
    可以通过如下方法获得该变量System.getProperty(“java.library.path”);

生成.h文件,javah -jni jnitool.JniTool

打印java线程的pid和tid,jvm层面

1
2
3
4
5
6
7
8
9
10
#include "jnitool_JniTool.h"
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

JNIEXPORT void JNICALL Java_jnitool_JniTool_getThreadID
(JNIEnv * env, jobject obj){
fprintf(stdout,"java_lock log PID = %d TID = %lu \n",getpid(),pthread_self());
fflush(stdout);//刷新,防止错位打印
}

使用make编译

1
2
3
4
5
6
vim makefile

libjnitools.so:
gcc -lpthread -fPIC -shared -o libjnitools.so -I /root/jdk1.8.0_221/include/ -I /root/jdk1.8.0_221/include/linux/ ./jnitools.c

make

对象头信息 object header 关闭指针压缩 -XX:-UseCompressedOops

  • 关于对象头信息的详情介绍,可以

  • 自己写个工具方法,根据markworld转换出头部信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    |--------------------------------------------------------------------------------------------------------------|
    | Object Header (128 bits) |
    |--------------------------------------------------------------------------------------------------------------|
    | Mark Word (64 bits) | Klass Word (64 bits) |
    |--------------------------------------------------------------------------------------------------------------|
    | unused:25 | identity_hashcode:31 | 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
    |--------------------------------------------------------------------------------------------------------------|
  • 接受JOL输出,自己写一个转换工具,方便debug

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    public void parseHeaderInfo(String printable) {
    String[] split = printable.replaceAll("[|)]", "").split("\n");
    StringBuilder builder = new StringBuilder();
    for (int i = 2; i <= 3; i++) {
    builder.append(split[i].split("\\(")[2]);
    }
    ArrayList<String> strings = new ArrayList<>(Arrays.asList(builder.toString().split(" ")));
    Collections.reverse(strings);
    builder = new StringBuilder();
    for (String string : strings) {
    builder.append(string);
    }
    String markWord = builder.toString();
    System.out.println("\n--------START-----------");
    //System.out.println("mark world:"+markWord);
    String lock = markWord.substring(62);
    String biased_lock = String.valueOf(markWord.charAt(61));
    builder = new StringBuilder();
    if (lock.equals("01")) {
    if (biased_lock.equals("0")) {
    System.out.println("--------无锁-----------");
    System.out.println("unused:25:" + markWord.substring(0, 25));
    System.out.println("identity_hashcode:31: " + markWord.substring(25, 56)
    + " (Hex:" + Integer.toHexString(Integer.parseInt(markWord.substring(25, 56), 2)) + ")");
    System.out.println("unused:1:" + markWord.substring(56, 57));
    System.out.println("age:4:" + markWord.substring(57, 61));
    System.out.println("biased_lock:1:" + markWord.substring(61, 62));
    System.out.println("lock:2:" + markWord.substring(62));

    } else {
    System.out.println("--------偏向锁--------");
    System.out.println("thread:54:" + markWord.substring(0, 54)
    +"(Dex:"+Long.parseLong(markWord.substring(0, 54), 2)+")");
    System.out.println("epoch:2:" + markWord.substring(54, 56));
    System.out.println("unused:1:" + markWord.substring(56, 57));
    System.out.println("age:4:" + markWord.substring(57, 61));
    System.out.println("biased_lock:1:" + markWord.substring(61, 62));
    }
    } else {
    String hexString = Long.toHexString(Long.parseLong(markWord.substring(0, 62), 2));
    if (lock.equals("00")) {
    System.out.println("--------轻量级锁--------");
    System.out.println("ptr_to_lock_record:62:" + markWord.substring(0, 62)
    + " (Hex:" + hexString + ")");
    System.out.println("lock:2:" + markWord.substring(62));
    } else if (lock.equals("10")) {
    System.out.println("--------重量级锁--------");
    System.out.println("ptr_to_heavyweight_monitor::62:" + markWord.substring(0, 62)
    + " (Hex:" + hexString + ")");
    System.out.println("lock:2:" + markWord.substring(62));
    } else if (lock.equals("11")) {
    System.out.println("--------GC标记--------");
    }
    }
    System.out.println("--------END-----------\n");
    }

3.无锁

  • JVM直接优化,进行无锁访问,虽然加了锁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    import jnitools.JniTools;
    import org.openjdk.jol.info.ClassLayout;

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;

    public class SyncTest {
    final Object lock = new Object();
    JniTools jniTool = new JniTools();

    public static void main(String[] args) throws InterruptedException {
    SyncTest test = new SyncTest();
    test.parseHeaderInfo((ClassLayout.parseInstance(test.lock).toPrintable()));
    System.out.println("---------------------------start--------------------");
    test.start();
    }

    public void start() throws InterruptedException {
    Runnable runnable = new Runnable() {
    int count = 0;

    @Override
    public void run() {
    while (true) {
    if (count < 100) {
    count++;
    } else {
    return;
    }
    try {
    lock();
    parseHeaderInfo(ClassLayout.parseInstance(lock).toPrintable());
    Thread.sleep(200);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
    };
    new Thread(runnable).start();
    Thread.sleep(5000);
    new Thread(runnable).start();
    }

    public void lock() {
    synchronized (lock) {
    jniTool.getTid();
    }
    }
    }
  • 分析,虽然有多个线程访问,但是不存在,同时竞争。


    log 无锁
    out.log

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    --------START-----------
    --------无锁-----------
    unused:25:0000000000000000000000000
    identity_hashcode:31: 0000000000000000000000000000000 (Hex:0)
    unused:1:0
    age:4:0000
    biased_lock:1:0
    lock:2:01
    --------END-----------

    ---------------------------start--------------------
    java_lock log-----------PID = 189 TID = 139775743776512

    --------START-----------
    --------无锁-----------
    unused:25:0000000000000000000000000
    identity_hashcode:31: 0000000000000000000000000000000 (Hex:0)
    unused:1:0
    age:4:0000
    biased_lock:1:0
    lock:2:01
    --------END-----------

    java_lock log-----------PID = 189 TID = 139775743776512

    --------START-----------
    --------无锁-----------
    unused:25:0000000000000000000000000
    identity_hashcode:31: 0000000000000000000000000000000 (Hex:0)
    unused:1:0
    age:4:0000
    biased_lock:1:0
    lock:2:01
    --------END-----------

    java_lock log-----------PID = 189 TID = 139775743776512

    --------START-----------
    --------无锁-----------
    unused:25:0000000000000000000000000
    identity_hashcode:31: 0000000000000000000000000000000 (Hex:0)
    unused:1:0
    age:4:0001
    biased_lock:1:0
    lock:2:01
    --------END-----------

4 偏向锁

  • 当关闭偏向锁功能时
  • 由于多个线程竞争偏向锁导致偏向锁升级为轻量级锁。
  • 一开始就是偏向锁,处于可偏向,谁先来锁,就偏向谁
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    import jnitools.JniTools;
    import org.openjdk.jol.info.ClassLayout;

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;

    public class SyncTest {
    final Object lock = new Object();
    JniTools jniTool = new JniTools();

    public static void main(String[] args) throws InterruptedException {
    Thread.sleep(5000);
    SyncTest test = new SyncTest();
    test.parseHeaderInfo((ClassLayout.parseInstance(test.lock).toPrintable()));
    System.out.println("---------------------------start--------------------");
    test.start();
    Thread.sleep(50000);
    }

    public void start() throws InterruptedException {
    Runnable runnable = new Runnable() {
    @Override
    public void run() {
    int count = 0;
    while (true) {
    if (count < 50) {
    count++;
    } else {
    System.out.println("Thread "+Thread.currentThread().getName()+" return ");
    return;
    }
    try {
    lock();
    parseHeaderInfo(ClassLayout.parseInstance(lock).toPrintable());
    // Thread.sleep(100);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
    };
    new Thread(runnable).start();
    Thread.sleep(5000);
    new Thread(runnable).start();
    }

    public void lock() {
    synchronized (lock) {
    jniTool.getTid();
    }
    }
    }
  • Thread.sleep(5000); 主要在线程启动时候加上了sleep

    out.log

  • 为了减少JVM启动时初始化时间,JVM默认延时加载偏向锁。大概为4s左右,具体时间因机器而异。当然我们也可以设置JVM参数 -XX:BiasedLockingStartupDelay=0 来取消延时加载偏向锁

  • 你会发现占用 thread 和 epoch 的 位置的均为0,说明当前偏向锁并没有偏向任何线程,此时这个偏向锁正处于可偏向状态。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    --------START-----------
    --------偏向锁--------
    thread:54:000000000000000000000000000000000000000000000000000000(Dex:0)
    epoch:2:00
    unused:1:0
    age:4:0000
    biased_lock:1:1
    --------END-----------

    ---------------------------start--------------------
    java_lock log-----------PID = 398 TID = 140630884325120

    --------START-----------
    --------偏向锁--------
    thread:54:000000000000000001111111111001110100010000100001101100(Dex:137335212140)
    epoch:2:00
    unused:1:0
    age:4:0001
    biased_lock:1:1
    --------END-----------

    java_lock log-----------PID = 398 TID = 140630884325120

    --------START-----------
    --------偏向锁--------
    thread:54:000000000000000001111111111001110100010000100001101100(Dex:137335212140)
    epoch:2:00
    unused:1:0
    age:4:0001
    biased_lock:1:1
    --------END-----------

  • Thread Thread-1 return 当线程2开始的时候,线程1已结结束了,这里怀疑线程复用,因为这里不满足线程重新偏向,且线程没有重新偏向

  • 偏向的线程没有变化,且pthread的TID的对象头的里的信息都没有变化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    java_lock log-----------PID = 398 TID = 140630884325120 

    --------START-----------
    --------偏向锁--------
    thread:54:000000000000000001111111111001110100010000100001101100(Dex:137335212140)
    epoch:2:00
    unused:1:0
    age:4:0001
    biased_lock:1:1
    --------END-----------

    Thread Thread-1 return
    java_lock log-----------PID = 398 TID = 140630884325120

    --------START-----------
    --------偏向锁--------
    thread:54:000000000000000001111111111001110100010000100001101100(Dex:137335212140)
    epoch:2:00
    unused:1:0
    age:4:0001
    biased_lock:1:1
    --------END-----------

5 轻量级锁

  • 我们让线程1放锁后,不让线程1退出

  • 让线程2去拿锁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48

    import jnitool.JniTool;
    import org.openjdk.jol.info.ClassLayout;

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;

    public class SyncTest {
    final Object lock = new Object();
    JniTool jniTool = new JniTool();

    public static void main(String[] args) throws InterruptedException {
    Thread.sleep(5000);
    SyncTest test = new SyncTest();
    test.parseHeaderInfo((ClassLayout.parseInstance(test.lock).toPrintable()));
    System.out.println("---------------------------start--------------------");
    test.start();
    }

    public void start() throws InterruptedException {
    Runnable runnable = new Runnable() {
    @Override
    public void run() {
    int count = 0;
    System.out.println(Thread.currentThread().getName() + " t1 start");
    while (true) {
    if (count < 50) {
    count++;
    } else {
    return;
    }
    lock();
    }
    }
    };
    new Thread(runnable).start();
    Thread.sleep(1000);//这里保证退出
    new Thread(runnable).start();
    }
    public void lock() {
    synchronized (lock) {
    Class<?> aClass = lock.getClass();
    jniTool.getThreadID();
    parseHeaderInfo(ClassLayout.parseInstance(lock).toPrintable());
    }
    }
    }
  • 线程1执行的时候,是偏向了线程1的

  • 线程1退出,线程2来的时候,直接升级了轻量级锁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    java_lock log  PID = 61919   TID = 123145379807232 

    --------START-----------Thread-1
    --------偏向锁--------
    thread:54:000000000000000001111111110000101010110100010011100100(Dex:137181742308)
    epoch:2:00
    unused:1:0
    age:4:0000
    biased_lock:1:1
    --------END-----------Thread-1

    Thread-2 t1 start
    java_lock log PID = 61919 TID = 123145379807232

    --------START-----------Thread-2
    --------轻量级锁--------
    ptr_to_lock_record:62:00000000000000000111000000000000000001001001111001111000001110 (Hex:1c0001279e0e)
    lock:2:00
    --------END-----------Thread-2

    java_lock log PID = 61919 TID = 123145379807232

6. 重量级锁

  • 偏向锁直接升级

  • 2个线程,同时并发访问

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    import jnitool.JniTool;
    import org.openjdk.jol.info.ClassLayout;

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;

    public class SyncTest {
    final Object lock = new Object();
    JniTool jniTool = new JniTool();

    public static void main(String[] args) throws InterruptedException {
    Thread.sleep(5000);
    SyncTest test = new SyncTest();
    test.parseHeaderInfo((ClassLayout.parseInstance(test.lock).toPrintable()));
    System.out.println("---------------------------start--------------------");
    test.start();
    }

    public void start() throws InterruptedException {
    Runnable runnable = new Runnable() {
    @Override
    public void run() {
    int count = 0;
    System.out.println(Thread.currentThread().getName() + " t1 start");
    while (true) {
    if (count < 50) {
    count++;
    } else {
    return;
    }
    lock();
    }
    }
    };
    new Thread(runnable).start();
    new Thread(runnable).start();
    }
    public void lock() {
    synchronized (lock) {
    Class<?> aClass = lock.getClass();
    jniTool.getThreadID();
    parseHeaderInfo(ClassLayout.parseInstance(lock).toPrintable());
    }
    }
    }
  • 从可偏向状态,直接升级为重量级锁。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    --------START-----------main
    --------偏向锁--------
    thread:54:000000000000000000000000000000000000000000000000000000(Dex:0)
    epoch:2:00
    unused:1:0
    age:4:0000
    biased_lock:1:1
    --------END-----------main

    ---------------------------start--------------------
    Thread-1 t1 start
    Thread-2 t1 start
    java_lock log PID = 61884 TID = 123145554096128

    --------START-----------Thread-1
    --------重量级锁--------
    ptr_to_heavyweight_monitor::62:00000000000000000111111110101101000111010000000110001011010010 (Hex:1feb474062d2)
    lock:2:10
    --------END-----------Thread-1

总结

  • 初始状态可能是偏向锁的可偏向状态,也可能是无锁状态
  • 初始锁,若只有一个线程连续的多次访问,构成锁的偏向
  • 不构成锁的重新偏向情况下,后面有多个线程轮流访问,不构成锁的竞争,锁升级到轻量级锁
  • 可以有连续状态,连续锁的升级。