java面试-偏向锁和轻量级锁

JDK1.6以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“轻量级锁”和“偏向锁”。在了解这些锁之前,要先了解一下对象头的结构。很多书中介绍过32位的对象头结构,但是对于64的说的很模糊,下面通过工具对64位的结构进行验证。

1. 查看对象结构和大小的工具jol-core:

在maven中添加依赖就可以使用了:

org.openjdk.jol
jol-core
0.10

使用:

System.out.println(ClassLayout.parseInstance(user).toPrintable());

在64位jvm中对象头有12个字节,也就是96位,前8个字节被称为一个字宽(markword),后4个字节为一个指针,指向描述原始对象的布局和行为的另一个对象(元对象),被称作klass。

MarkWord klass
64 16

下面是对象头的中的Mark Word布局,之后会进行详细的介绍:

锁状态/gc Mark Word(64 bits) 是否偏向 锁标志位
无锁 unused:25 hashcode:31 unused:1 age:4 0 01
偏向锁 threadId:54 epoch:2 unused:1 age:4 1 01
轻量级锁 指向栈中锁记录的指针:62 - 00
重量级锁 指向互斥量(重量级锁)的指针:62 - 10
gc标记 - - 11

2. 验证锁状态

2.1 验证无锁状态

可以两种方法得到无锁状态的对象:

(1)默认情况下偏向锁开启(jvm参数为:-XX:+UseBiasedLocking)时,只创建对象并打印对象头,这种情况下偏向锁还未激活。
(2)关闭偏向锁参数,设置jvm参数为:-XX:-UseBiasedLocking。
public class LockTest {

    static ObjectLock lock;

    public static void main(String[] args) throws Exception {
        lock = new ObjectLock();
        System.out.println("befre lock");
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
    }

}

class ObjectLock {

    String name;
}

输出:


wus.jpg

上图中表红的3位,分别为:

是否偏向 锁标志位
0 01

可能有人感到奇怪,为什么是前3位呢?因为是小端存储,感兴趣的可以网上搜一下“大端存储与小端存储”。这里的是反着的。
通过实验可以看出,如果偏向锁没有开启或者开启了未激活时,新创建的对象是无锁状态。
无锁状态下还有31位的hashcode,但是上面的输出中那31位都是0,这是因为hashcode还没有计算,调用一下hashcode方法就可以看到了。

public class LockTest {

    static ObjectLock lock;

    public static void main(String[] args) throws Exception {
        lock = new ObjectLock();
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
        System.out.println("hashcode:: "+Integer.toBinaryString(lock.hashCode()));
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
    }

}

class ObjectLock {

    String name;
}

输出:


wuhashcode.jpg

可以看到调用hashcode后,对应位置上和对象的hashcode是一样的。

2.2 验证偏向锁状态

是否启用偏向锁可以通过参数(UseBiasedLocking)控制,jdk1.6以后默认都是开启的,但是注意是在程序运行几秒之后才激活。所以,要看偏向锁状态必须保证开启偏向锁参数并且激活偏向锁。

public class LockTest {

    static ObjectLock lock;

    public static void main(String[] args) throws Exception {
        Thread.sleep(5000);
        lock = new ObjectLock();
        System.out.println("befre lock");
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
    }

}

class ObjectLock {

    String name;
}

输出:


pian.jpg

上图中表红的3位,分别为:

是否偏向 锁标志位
1 01

就是偏向锁状态。但是其他都为0,这是因为没有对对象进行加锁操作,下面我们进行加锁测试验证threadId+epoch。

    public static void main(String[] args) throws Exception {
        Thread.sleep(5000);
        lock = new ObjectLock();
        synchronized (lock) {
            // 第一次加锁
            System.out.println(ClassLayout.parseInstance(lock).toPrintable());
        }
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
        synchronized (lock) {
            // 第二次加锁
            System.out.println(ClassLayout.parseInstance(lock).toPrintable());
        }
    }

输出:

# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
orderTest.ObjectLock object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 70 80 c4 (00000101 01110000 10000000 11000100) (-998215675)
      4     4                    (object header)                           a2 7f 00 00 (10100010 01111111 00000000 00000000) (32674)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4   java.lang.String ObjectLock.name                           null
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

orderTest.ObjectLock object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 70 80 c4 (00000101 01110000 10000000 11000100) (-998215675)
      4     4                    (object header)                           a2 7f 00 00 (10100010 01111111 00000000 00000000) (32674)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4   java.lang.String ObjectLock.name                           null
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

orderTest.ObjectLock object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 70 80 c4 (00000101 01110000 10000000 11000100) (-998215675)
      4     4                    (object header)                           a2 7f 00 00 (10100010 01111111 00000000 00000000) (32674)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4   java.lang.String ObjectLock.name                           null
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

可以看到3次输出是一样因为都是在同一个线程中,而且偏向锁没有明显的锁释放过程,只有锁的撤销。第一次加锁后把线程id写入对象头中,第二次进入同步块时只是验证threadid是否相同,发现相同直接进入。
总结:
(1)当代码进入同步快前,如果为可偏向状态(即对象的初始状态,没有偏向过任何线程,threadid为空),则尝试用 CAS 操作, 将自己的线程 ID 写入MarkWord。如果CAS成功则进入同步快执行代码;如果CAS失败则说明有其他的线程B获取了偏向锁,此时需要撤销 线程 B 获得的偏向锁,将 线程 B 持有的锁升级为轻量级锁。这个过程后面会进行详细介绍。
(2)当代码进入同步快前,如果为已经偏向状态(即上面代码中的过程,对象偏向过一个线程),则检测 MarkWord 中存储的 thread ID 是否等于当前 thread ID ,如果相等直接进入代码块,如果不相等撤销偏向锁。

3.偏向锁转为轻量级锁

3.1 偏向锁的撤销过程

其实偏向锁的撤销过程不是恢复到无锁状态,而是偏向锁升级到轻量级锁的过程。撤销偏向锁是发生在安全点(停止所有线程的执行),当到达安全点时通过Mark Word中记录的线程id找到对应的线程:
(1)在当前线程的栈桢(Stack Frame)中创建用于存储锁记录(lock record)的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。
(2)然后线程尝试使用 CAS 操作将对象头中的 Mark Word 替换为指向锁记录的指针。
如果成功,当前线程获得锁;如果失败,表示该对象已经被加锁了, 先进行自旋操作, 再次尝试 CAS 争抢, 如果仍未争抢到, 则进一步升级锁至重量级锁。

3.2 偏向锁升级为轻量级锁

其实偏向锁撤销的同时就是升级为轻量锁的过程,下面做一个简单的演示:

public static void main(String[] args) throws Exception {
        Thread.sleep(5000);
        lock = new ObjectLock();
        synchronized (lock) {
            // 第一次加锁
            System.out.println(ClassLayout.parseInstance(lock).toPrintable());
        }
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
//        Thread.sleep(5000);
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                // 第二次加锁
                System.out.println(ClassLayout.parseInstance(lock).toPrintable());
            }
        });
        t1.start();
        Thread.sleep(5000);
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());

    }

输出:

# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
orderTest.ObjectLock object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 18 00 e8 (00000101 00011000 00000000 11101000) (-402647035)
      4     4                    (object header)                           b6 7f 00 00 (10110110 01111111 00000000 00000000) (32694)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4   java.lang.String ObjectLock.name                           null
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

orderTest.ObjectLock object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 18 00 e8 (00000101 00011000 00000000 11101000) (-402647035)
      4     4                    (object header)                           b6 7f 00 00 (10110110 01111111 00000000 00000000) (32694)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4   java.lang.String ObjectLock.name                           null
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

orderTest.ObjectLock object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           50 18 f3 01 (01010000 00011000 11110011 00000001) (32708688)
      4     4                    (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4   java.lang.String ObjectLock.name                           null
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

orderTest.ObjectLock 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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4   java.lang.String ObjectLock.name                           null
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

4个输出,第一个是在主线程中加锁,第二个是释放锁后,第三个是新建一个线程加锁,第四个是新建线程释放锁后。
可以看出输出第一、二都是偏向锁,新建一个线程后因为是不同的线程,所以释放偏向锁并升级为轻量级锁,释放锁后恢复为无锁状态(因为锁是不能降级的)。
目前只有这种是轻量级状态的,其他的情况只要是存在竞争就会转化为重量级锁。其实,之前说过,“如果失败,表示该对象已经被加锁了, 先进行自旋操作, 再次尝试 CAS 争抢, 如果仍未争抢到, 则进一步升级锁至重量级锁。”,这个自旋的过程其实很短暂的,实验程序基本是跑不出来的,看到的就是直接到重量级锁了。

3.3 重量级锁

下面程序模拟了线程直接的竞争锁的情况,线程1先获取锁,然后在还没释放锁时线程2进行抢锁。

public static void main(String[] args) throws Exception {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");

        Thread.sleep(5000);
        lock = new ObjectLock();
        Thread t1 = new Thread(() -> {
            System.out.println(df.format(new Date()) + "  Thread1 try lock ======> " + ClassLayout
                    .parseInstance(lock)
                    .toPrintable());

            synchronized (lock) {
                System.out.println(
                        df.format(new Date()) + "  Thread1 lock ======> " + ClassLayout
                                .parseInstance(lock)
                                .toPrintable());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(
                        df.format(new Date()) + "  Thread1:: " + ClassLayout.parseInstance(lock)
                                .toPrintable());
            }
        });
        Thread t2 = new Thread(() -> {

            System.out.println(
                    df.format(new Date()) + "  Thread2 try lock ======> " + ClassLayout
                            .parseInstance(lock)
                            .toPrintable());

            synchronized (lock) {
                System.out.println(
                        df.format(new Date()) + "  Thread2 lock:: " + ClassLayout
                                .parseInstance(lock)
                                .toPrintable());
            }
        });
        t1.start();
        Thread.sleep(1000);
        t2.start();
        Thread.sleep(5000);
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());

    }

输出:

2020-09-04 10:51:16:184  Thread1 try lock ======> orderTest.ObjectLock 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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4   java.lang.String ObjectLock.name                           null
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

2020-09-04 10:51:16:953  Thread1 lock ======> orderTest.ObjectLock object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 c8 90 0e (00000101 11001000 10010000 00001110) (244369413)
      4     4                    (object header)                           b0 7f 00 00 (10110000 01111111 00000000 00000000) (32688)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4   java.lang.String ObjectLock.name                           null
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

2020-09-04 10:51:17:187  Thread2 try lock ======> orderTest.ObjectLock object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 c8 90 0e (00000101 11001000 10010000 00001110) (244369413)
      4     4                    (object header)                           b0 7f 00 00 (10110000 01111111 00000000 00000000) (32688)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4   java.lang.String ObjectLock.name                           null
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

2020-09-04 10:51:19:956  Thread1:: orderTest.ObjectLock object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           5a 69 83 0c (01011010 01101001 10000011 00001100) (209938778)
      4     4                    (object header)                           b0 7f 00 00 (10110000 01111111 00000000 00000000) (32688)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4   java.lang.String ObjectLock.name                           null
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

2020-09-04 10:51:19:958  Thread2 lock:: orderTest.ObjectLock object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           5a 69 83 0c (01011010 01101001 10000011 00001100) (209938778)
      4     4                    (object header)                           b0 7f 00 00 (10110000 01111111 00000000 00000000) (32688)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4   java.lang.String ObjectLock.name                           null
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

orderTest.ObjectLock 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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4   java.lang.String ObjectLock.name                           null
Instance size: 16 bytes

程序运行时间表:

10:51:16:184 10:51:16:953 10:51:17:187 10:51:19:956 10:51:19:958 10:51:19:958
t1尝试获取lock 偏向锁 t1获取到lock 偏向锁 t2尝试获取lock 偏向锁 t1由于t2的竞争升级为重量级锁 t2获取到锁 重量级锁 锁释放后 无锁状态

3.4 总结
总结一下,其实无锁->偏向锁->轻量级锁->重量级锁的转化过程中没那么复杂,注意记住:
(1)只有一个线程获取锁时,就是偏向锁。
(2)多个线程时,不存在竞争(多个线程顺序执行),轻量级锁。
(3)多个线程存在竞争时重量级锁。
很多时候,偏向锁->轻量级锁->重量级锁的转化过程,基本你只能看见开头和结尾,因为轻量级锁的自旋被优化不会长时间持续进行的,所以你看到的就是偏向锁到重量级锁的过程。

你可能感兴趣的:(java面试-偏向锁和轻量级锁)