【Java 多线程】从对象头观察锁的升级

1,007 阅读2分钟

Markword

如下图,Java中的对象有对象头header这么一个东西,用来记录这个对象的各种信息,类指针啊什么的,对象头中又有markword这么一个东东,这里面记录了锁信息。俗称的把对象上锁,就是修改markword中的锁信息。

markword里面的东西长啥样呢?其实里面就是一堆二进制,如下图:

锁升级

Markword中的某几位用来表示锁的状态,具体的对照表如下:

眼见为实

Object o = new Object();
System.out.println(ClassLayout.parseInstance(t).toPrintable());

synchronized(o) {
	System.out.println(ClassLayout.parseInstance(t).toPrintable());
{

上述代码中,在对象o刚被创建出来时,让我们看看它的对象头: 红线标记处为001,代表无锁状态,因为它刚被创建。

使用synchronized给对象o加锁后,再来看看o的对象头: 红线标记处为00,代表了轻量级锁,括号内数字代表了占有此锁的线程。

如果让两个线程抢一把锁会发生什么?上代码:

Object o = new Object();
new Thread(() -> {
    synchronized (o) {
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }
}).start();

new Thread(() -> {
    synchronized (o) {
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }
}).start();

看看对象头:

红线部分显示01, 说明如果两个或以上数量的线程抢一把锁,锁会升级为重量级锁

为什么要有锁升级这个过程呢?

那为什么不直接上来就上轻量级锁或者重量级锁呢?
这是因为当线程执行到被锁住的代码块时,锁的获取是会消耗资源的。轻量级锁的实现原理是CAS自旋,就像while loop 一样,消耗CPU资源。重量级锁需要操作系统调度机制接入,也会消耗资源。而大部分时候只有一个线程默默的干活,并不存在锁竞争,每次这个孤单的线程执行一块上了轻量级锁锁住的代码时需要过一遍CAS,执行一块重量级锁锁住的代码时需要等操作系统的发令,自己和自己竞争一下,这效率就有点低了。
当一个对象还没有上锁时,偏向锁会直接来往markword里贴上线程ID,表示锁住了,没有CAS自旋和操作系统调用这些耗时的锁竞争机制,提高了性能。 偏向锁假定了这对象还没锁,所以偏向锁又叫做乐观锁

所以说到这里,锁的升级过程就明了了: 无锁->偏向锁->轻量级锁->重量级锁

Resource: