概述

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

  1. static final Object obj = new Object();
  2. public static void m1() {
  3. synchronized( obj ) {
  4. // 同步块 A
  5. m2();
  6. }
  7. }
  8. public static void m2() {
  9. synchronized( obj ) {
  10. // 同步块 B
  11. m3();
  12. }
  13. }
  14. public static void m3() {
  15. synchronized( obj ) {
  16. // 同步块 C
  17. }
  18. }

如上,轻量级锁每次调用synchronized,都会用锁记录来替换markword,而偏向锁会用当前的线程id来替换对象的markword,之后只要判断线程id是不是自己就行了

一个对象创建时:

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

    测试延迟特性

    public class Test_BiaseLock1 {
      public static void main(String[] args) throws InterruptedException {
          Object o1 = new Object();
          synchronized (o1) {
              log.debug(SimpleClassLayout.parseInstance(o1).toPrintSimple());
          }
          TimeUnit.SECONDS.sleep(5);
          Object o2 = new Object();
          synchronized (o2) {
              log.debug(SimpleClassLayout.parseInstance(o2).toPrintSimple());
          }
      }
    }
    
  • 结果

    22:59:52.814 [main] DEBUG lock1 - 
    64位Mark Word信息: 00000000 00000000 00000000 00000000 00000011 00010100 11110000 11110000 
    锁标识位为: 000
    锁状态为: 轻量级锁、自旋锁
    22:59:57.817 [main] DEBUG lock1 - 
    64位Mark Word信息: 00000000 00000000 00000000 00000000 00000011 00110001 00111000 00000101 
    锁标识位为: 101
    锁状态为: 偏向锁
    

    测试偏向锁

  • vmOption添加参数

    -XX:BiasedLockingStartupDelay=0
    
@Slf4j(topic = "lock2")
public class Test_BiaseLock2 {
    public static void main(String[] args) {
        Object o = new Object();
        log.debug("synchronized前:"+SimpleClassLayout.parseInstance(o).toPrintSimple());
        synchronized (o) {
            log.debug("synchronized中:"+SimpleClassLayout.parseInstance(o).toPrintSimple());
        }
        log.debug("synchronized后:"+SimpleClassLayout.parseInstance(o).toPrintSimple());
    }
}
  • 结果
    23:04:25.704 [main] DEBUG lock2 - synchronized前:
    64位Mark Word信息: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
    锁标识位为: 101
    锁状态为: 偏向锁
    23:04:25.706 [main] DEBUG lock2 - synchronized中:
    64位Mark Word信息: 00000000 00000000 00000000 00000000 00000010 11110000 00111000 00000101 
    锁标识位为: 101
    锁状态为: 偏向锁
    23:04:25.707 [main] DEBUG lock2 - synchronized后:
    64位Mark Word信息: 00000000 00000000 00000000 00000000 00000010 11110000 00111000 00000101 
    锁标识位为: 101
    锁状态为: 偏向锁
    

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

测试禁用

  • 上一节代码添加vmOption参数

    -XX:-UseBiasedLocking
    
  • 结果

    23:06:05.331 [main] DEBUG lock2 - synchronized前:
    64位Mark Word信息: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
    锁标识位为: 001
    锁状态为: 无锁
    23:06:05.341 [main] DEBUG lock2 - synchronized中:
    64位Mark Word信息: 00000000 00000000 00000000 00000000 00000010 01101101 11110000 10111000 
    锁标识位为: 000
    锁状态为: 轻量级锁、自旋锁
    23:06:05.341 [main] DEBUG lock2 - synchronized后:
    64位Mark Word信息: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
    锁标识位为: 001
    锁状态为: 无锁
    

    测试 hashCode

  • 正常状态对象一开始是没有 hashCode 的,第一次调用才生成

  • vmOption添加参数

    -XX:BiasedLockingStartupDelay=0
    
    @Slf4j(topic = "lock3")
    public class Test_BiaseLock3 {
      public static void main(String[] args) {
          Object o = new Object();
          o.hashCode();
          log.debug("synchronized前:"+SimpleClassLayout.parseInstance(o).toPrintSimple());
          synchronized (o) {
              log.debug("synchronized中:"+SimpleClassLayout.parseInstance(o).toPrintSimple());
          }
          log.debug("synchronized后:"+SimpleClassLayout.parseInstance(o).toPrintSimple());
      }
    }
    
  • 结果

    23:07:54.402 [main] DEBUG lock3 - synchronized前:
    64位Mark Word信息: 00000000 00000000 00000000 01100001 10010011 10111000 01000101 00000001 
    锁标识位为: 001
    锁状态为: 无锁
    23:07:54.405 [main] DEBUG lock3 - synchronized中:
    64位Mark Word信息: 00000000 00000000 00000000 00000000 00000010 11100100 11110011 01011000 
    锁标识位为: 000
    锁状态为: 轻量级锁、自旋锁
    23:07:54.405 [main] DEBUG lock3 - synchronized后:
    64位Mark Word信息: 00000000 00000000 00000000 01100001 10010011 10111000 01000101 00000001 
    锁标识位为: 001
    锁状态为: 无锁
    
  • 发现没有使用偏向锁

    原理image.png

    在没有调用hashcode时,hashcode都是0
    调用了hashcode,hashcode就不为0了,即便开启了偏向锁,但是偏向锁需要占用存储hashcode的位来存储线程id,和Epoch,这样hashcode就消失了,因此有hashcode的对象无法被偏向

  • 轻量级锁会在锁记录中记录 hashCode

  • 重量级锁会在 Monitor 中记录 hashCode

测试偏向锁升级轻量级锁

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

package me.ezerror.mutilthreading.D0.Ch03;

import lombok.extern.slf4j.Slf4j;
import me.ezerror.util.simpleclasslayout.SimpleClassLayout;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "lock4")
public class Test_BiaseLock4 {
    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        log.debug("synchronized前:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
        synchronized (o) {
            log.debug("synchronized中:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
        }
        log.debug("synchronized后:" + SimpleClassLayout.parseInstance(o).toPrintSimple());

        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{
            log.debug("synchronized前:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
            synchronized (o) {
                log.debug("synchronized中:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
            }
            log.debug("synchronized后:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
        },"t2").start();
    }
}
  • 结果 ```java 23:24:44.337 [main] DEBUG lock4 - synchronized前: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 锁标识位为: 101 锁状态为: 偏向锁 23:24:44.337 [main] DEBUG lock4 - synchronized中: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000010 10010010 00111000 00000101 锁标识位为: 101 锁状态为: 偏向锁 23:24:44.337 [main] DEBUG lock4 - synchronized后: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000010 10010010 00111000 00000101 锁标识位为: 101 锁状态为: 偏向锁

23:24:45.350 [t2] DEBUG lock4 - synchronized前: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000010 10010010 00111000 00000101 锁标识位为: 101 锁状态为: 偏向锁 23:24:45.350 [t2] DEBUG lock4 - synchronized中: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00101001 10011000 11101101 01010000 锁标识位为: 000 锁状态为: 轻量级锁、自旋锁 23:24:45.350 [t2] DEBUG lock4 - synchronized后: 64位Mark Word信息: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 锁标识位为: 001 锁状态为: 无锁

<a name="jvEqR"></a>
# 测试偏向锁撤销 - 调用 wait/notify
```java
@Slf4j(topic = "lock5")
public class Test_BiaseLock5 {
    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        new Thread(() -> {
            log.debug(SimpleClassLayout.parseInstance(o).toPrintSimple());
            synchronized (o) {
                log.debug("wait前:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
                try {
                    o.wait();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("wait后:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
            }
        }, "t1").start();
          // 等待t1执行到wait
        TimeUnit.SECONDS.sleep(3);
        new Thread(() -> {
            synchronized (o) {
                log.debug("notify");
                o.notify();
            }
        }, "t2").start();
        // 等待t1执行完毕
        TimeUnit.SECONDS.sleep(1);
        log.debug("最终:" + SimpleClassLayout.parseInstance(o).toPrintSimple());
    }
}
  • 结果

    23:36:35.036 [t1] DEBUG lock5 - 
    64位Mark Word信息: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
    锁标识位为: 101
    锁状态为: 偏向锁
    23:36:35.036 [t1] DEBUG lock5 - wait前:
    64位Mark Word信息: 00000000 00000000 00000000 00000000 00100111 11101101 10001000 00000101 
    锁标识位为: 101
    锁状态为: 偏向锁
    23:36:36.036 [t2] DEBUG lock5 - notify
    23:36:36.036 [t1] DEBUG lock5 - wait后:
    64位Mark Word信息: 00000000 00000000 00000000 00000000 00100110 01000110 00100011 11001010 
    锁标识位为: 010
    锁状态为: 重量级锁
    23:36:37.040 [main] DEBUG lock5 - 最终:
    64位Mark Word信息: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
    锁标识位为: 001
    锁状态为: 无锁
    

    批量重偏向

  • 如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID

  • 当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程
  • 添加参数

    -XX:BiasedLockingStartupDelay=0 
    -XX:BiasedLockingBulkRebiasThreshold=5 //偏向锁批量重偏向阈值
    
    @Slf4j(topic = "lock6")
    public class Test_BiaseLock6 {
      static Thread t1;
      static Thread t2;
    
      public static void main(String[] args) throws InterruptedException {
          Vector<Lock> list = new Vector<>();
          int loopNumber = 5;
          t1 = new Thread(() -> {
              for (int i = 0; i < loopNumber; i++) {
                  Lock o = new Lock();
                  list.add(o);
                  synchronized (o) {
                      log.debug(++i + "\t" + SimpleClassLayout.parseInstance(o).toPrintSimple(false));
                  }
                  i--;
              }
              LockSupport.unpark(t2);
          }, "t1");
          t1.start();
    
          t2 = new Thread(() -> {
              LockSupport.park();
              log.debug("================================");
              for (int i = 0; i < loopNumber; i++) {
                  Lock o = list.get(i++);
                  synchronized (o) {
                  }
                  log.debug(i-- + "\t" + SimpleClassLayout.parseInstance(o).toPrintSimple(false));
              }
          }, "t2");
          t2.start();
          t2.join();
          log.debug(SimpleClassLayout.parseInstance(new Lock()).toPrintSimple(false));
      }
    
      static class Lock {
    
      }
    }
    
  • 结果

    [t1] DEBUG lock6 - 1    00000000 00000000 00000000 00000000 00101001 11100110 11101000 00000101 
    [t1] DEBUG lock6 - 2    00000000 00000000 00000000 00000000 00101001 11100110 11101000 00000101 
    [t1] DEBUG lock6 - 3    00000000 00000000 00000000 00000000 00101001 11100110 11101000 00000101 
    [t1] DEBUG lock6 - 4    00000000 00000000 00000000 00000000 00101001 11100110 11101000 00000101 
    [t1] DEBUG lock6 - 5    00000000 00000000 00000000 00000000 00101001 11100110 11101000 00000101 
    [t2] DEBUG lock6 - ================================
    [t2] DEBUG lock6 - 1    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
    [t2] DEBUG lock6 - 2    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
    [t2] DEBUG lock6 - 3    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
    [t2] DEBUG lock6 - 4    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
    [t2] DEBUG lock6 - 5    00000000 00000000 00000000 00000000 00101001 11100110 11111001 00000101
    
  • 发现第五个开始就重偏向了

    批量撤销

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

  • 添加参数

    -XX:BiasedLockingStartupDelay=0 
    -XX:BiasedLockingBulkRevokeThreshold=5 //偏向锁批量撤销阈值
    

    ```java package me.ezerror.mutilthreading.D0.Ch03;

import lombok.extern.slf4j.Slf4j; import me.ezerror.util.simpleclasslayout.SimpleClassLayout; import org.openjdk.jol.info.ClassLayout;

import java.util.Vector; import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = “lock6”) public class Test_BiaseLock7 { static Thread t1; static Thread t2; static Thread t3;

public static void main(String[] args) throws InterruptedException {

    Vector<Lock> list = new Vector<>();
    int loopNumber = 4;
    t1 = new Thread(() -> {
        for (int i = 0; i < loopNumber; i++) {
            int h = i + 1;
            Lock o = new Lock();
            list.add(o);
            synchronized (o) {
                log.debug(h + "\t" + SimpleClassLayout.parseInstance(o).toPrintSimple(false));
            }
        }
        LockSupport.unpark(t2);
    }, "t1");
    t1.start();

    t2 = new Thread(() -> {
        LockSupport.park();
        log.debug("================================");
        for (int i = 0; i < loopNumber; i++) {
            int h = i + 1;
            Lock o = list.get(i);
            //  log.debug(i + "\t" + SimpleClassLayout.parseInstance(o).toPrintSimple(false));
            synchronized (o) {
                //  log.debug(i + "\t" + SimpleClassLayout.parseInstance(o).toPrintSimple(false));
            }
            log.debug(h + "\t" + SimpleClassLayout.parseInstance(o).toPrintSimple(false));
        }
        LockSupport.unpark(t3);
    }, "t2");
    t2.start();
    t2.join();
    log.debug(SimpleClassLayout.parseInstance(new Lock()).toPrintSimple(false));
}

public static class Lock {

}

}


- 结果
```java
[t1] DEBUG lock6 - 1    00000000 00000000 00000000 00000000 00101010 00010001 01110000 00000101 
[t1] DEBUG lock6 - 2    00000000 00000000 00000000 00000000 00101010 00010001 01110000 00000101 
[t1] DEBUG lock6 - 3    00000000 00000000 00000000 00000000 00101010 00010001 01110000 00000101 
[t1] DEBUG lock6 - 4    00000000 00000000 00000000 00000000 00101010 00010001 01110000 00000101 
[t1] DEBUG lock6 - 5    00000000 00000000 00000000 00000000 00101010 00010001 01110000 00000101 
[t2] DEBUG lock6 - ================================
[t2] DEBUG lock6 - 1    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] DEBUG lock6 - 2    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] DEBUG lock6 - 3    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] DEBUG lock6 - 4    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] DEBUG lock6 - 5    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[main] DEBUG lock6 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
  • 发现撤销了5个之后,再生成对象已经不使用偏向锁了
  • 循环改为四次,再执行

    [t1] DEBUG lock6 - 1    00000000 00000000 00000000 00000000 00101001 00000001 10011000 00000101 
    [t1] DEBUG lock6 - 2    00000000 00000000 00000000 00000000 00101001 00000001 10011000 00000101 
    [t1] DEBUG lock6 - 3    00000000 00000000 00000000 00000000 00101001 00000001 10011000 00000101 
    [t1] DEBUG lock6 - 4    00000000 00000000 00000000 00000000 00101001 00000001 10011000 00000101 
    [t2] DEBUG lock6 - ================================
    [t2] DEBUG lock6 - 1    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
    [t2] DEBUG lock6 - 2    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
    [t2] DEBUG lock6 - 3    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
    [t2] DEBUG lock6 - 4    00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
    [main] DEBUG lock6 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
    
  • 发现此时还是可以使用偏向锁的