偏向锁

偏向锁

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有。

image-20210128200802772

偏向状态

回忆一下对象头格式

image-20210128200834714

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的thread、epoch、age 都为 0
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -XX:BiasedLockingStartupDelay=0 来禁用延迟
  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值

测试偏向锁


/**
 * @author WGR
 * @create 2021/1/28 -- 15:20
 */
@Slf4j(topic = "c.TestBiased")
public class TestBiased {

    public static void main(String[] args) {
        Dog d = new Dog();
        log.debug( ClassLayout.parseInstance(d).toPrintable());
        sleep(3);
        log.debug( ClassLayout.parseInstance(new Dog()).toPrintable());
    }
}

class Dog{

}

image-20210128153437717

因为偏向锁有几秒的延迟。-XX:BiasedLockingStartupDelay=0

image-20210128153842578

当取消延迟后发现,2个锁标准都是101,变成了偏向锁。

@Slf4j(topic = "c.TestBiased")
public class TestBiased {

    public static void main(String[] args) {
        Dog d = new Dog();
        log.debug( ClassLayout.parseInstance(d).toPrintable());
        synchronized (d){
            log.debug( ClassLayout.parseInstance(d).toPrintable());
        }
        log.debug( ClassLayout.parseInstance(d).toPrintable());
    }
}

class Dog{

}

image-20210128153907672

注意:处于偏向锁的对象解锁后,线程 id 仍存储于对象头中

测试禁用

在上面测试代码运行时在添加 VM 参数 -XX:-UseBiasedLocking 禁用偏向锁

image-20210128154237202

禁用后直接是轻量级锁。

撤销 -HashCode

调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销
轻量级锁会在锁记录中记录 hashCode,重量级锁会在 Monitor 中记录 hashCode.

/**
 * @author WGR
 * @create 2021/1/28 -- 15:20
 */
@Slf4j(topic = "c.TestBiased")
public class TestBiased {

    public static void main(String[] args) {


        Dog d = new Dog();

        d.hashCode();
        log.debug( ClassLayout.parseInstance(d).toPrintable());
        synchronized (d){
            log.debug( ClassLayout.parseInstance(d).toPrintable());
        }
        log.debug( ClassLayout.parseInstance(d).toPrintable());

    }
}

class Dog{

}

image-20210128204559553

撤销 - 其它线程使用对象

当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁


/**
 * @author WGR
 * @create 2021/1/28 -- 15:20
 */
@Slf4j(topic = "c.TestBiased")
public class TestBiased {

    public static void main(String[] args) {


        Dog d = new Dog();

        new Thread(() ->{
            log.debug( ClassLayout.parseInstance(d).toPrintable());
            synchronized (d){
                log.debug( ClassLayout.parseInstance(d).toPrintable());
            }
            log.debug( ClassLayout.parseInstance(d).toPrintable());
            synchronized (TestBiased.class) {
                TestBiased.class.notify();
            }
        },"t1").start();

        new Thread(() ->{
            synchronized (TestBiased.class){
                try {
                    TestBiased.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            log.debug( ClassLayout.parseInstance(d).toPrintable());
            synchronized (d){
                log.debug( ClassLayout.parseInstance(d).toPrintable());
            }
            log.debug( ClassLayout.parseInstance(d).toPrintable());
        },"t2").start();

    }
}

class Dog{

}

结果

16:08:42.918 c.TestBiased [t1] - com.dalianpai.thread.Dog 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)                           a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

16:08:42.921 c.TestBiased [t1] - com.dalianpai.thread.Dog object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 b0 9f 1e (00000101 10110000 10011111 00011110) (513781765)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

16:08:42.922 c.TestBiased [t1] - com.dalianpai.thread.Dog object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 b0 9f 1e (00000101 10110000 10011111 00011110) (513781765)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

16:08:42.923 c.TestBiased [t2] - com.dalianpai.thread.Dog object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 b0 9f 1e (00000101 10110000 10011111 00011110) (513781765)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

16:08:42.924 c.TestBiased [t2] - com.dalianpai.thread.Dog object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           80 f2 1d 1f (10000000 11110010 00011101 00011111) (522056320)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

16:08:42.925 c.TestBiased [t2] - com.dalianpai.thread.Dog 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)                           a4 9b 01 f8 (10100100 10011011 00000001 11111000) (-134112348)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes

批量重偏向

如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程

private static void test3() throws InterruptedException {
Vector<Dog> list = new Vector<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 30; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "	" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
synchronized (list) {
list.notify();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (list) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("===============> ");
for (int i = 0; i < 30; i++) {
Dog d = list.get(i);
log.debug(i + "	" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "	" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "	" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t2");
t2.start();
}

批量撤销

当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的

static Thread t1,t2,t3;
private static void test4() throws InterruptedException {
Vector<Dog> list = new Vector<>();
int loopNumber = 39;
t1 = new Thread(() -> {
for (int i = 0; i < loopNumber; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "	" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
LockSupport.unpark(t2);
}, "t1");
t1.start();
t2 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "	" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "	" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "	" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
LockSupport.unpark(t3);
}, "t2");
    t2.start();
t3 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "	" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "	" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "	" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t3");
t3.start();
t3.join();
log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
}
原文地址:https://www.cnblogs.com/dalianpai/p/14341912.html