syncronized原理

syncronized是基于java对象加锁的。java对象创建后jvm会给该对象维护一个管程(Monitor)对象, Monitor最终又依赖操作系统维护的Mutex(互斥量)进行一系列锁操作, 比如: Monitor.Enter(获取锁)、Monitor.Exit(释放锁)等。

syncronized会被编译成monitorenter和monitorexit两条指令。其实wait/notify也是依赖于monitor对象的,这也是它们只能在同步块或同步方法中才能被调用的原因。

  • monitor在Java虚拟机(HotSpot)中的实现
    ObjectMonitor() {
    _header = NULL;
    _count = 0; // 记录线程获取锁的次数
    _waiters = 0,
    _recursions = 0;
    _object = NULL;
    _owner = NULL; // 指向持有ObjectMonitor对象的线程
    _WaitSet = NULL; // java对象调用wait方法时,处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock = 0 ;
    _Responsible = NULL ;
    _succ = NULL ;
    _cxq = NULL ;
    FreeNext = NULL ;
    _EntryList = NULL ; // 处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq = 0 ;
    _SpinClock = 0 ;
    OwnerIsThread = 0 ;
    }

锁升级过程

早期版本, syncronized加锁时直接使用mutex加一个重量级锁, 自jdk1.5之后, syncronized优化了加锁过程,过程如下:

  • 当单线程获取对象锁时,此时只会加偏向锁
  • 当多线程获取对象锁时,线程在没有获取到锁时通过自旋等待后成功获取到锁,此时升级到轻量级锁。
  • 当多线程获取对象锁时,线程在没有获取到锁且自旋等待后也未获取到锁,此时升级到重量级锁。

以下是Mark Word在32位虚拟机上的不同状态:
Mark Word

我们再通过示例程序来看看变化过程

引入jol包

<dependency>
  <groupId>org.openjdk.jol</groupId>
  <artifactId>jol-core</artifactId>
  <version>0.12</version>
</dependency>
  • 无锁-001
Object obj = new Object();
// 00000001 00000000 00000000 00000000 (小端模式输出)
// 00000000 00000000 00000000 00000001 (大端模式输出)
// 此处对象的hashCode没有打印出来是因为打印方式是懒打印,由C语言实现的
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
  • 偏向锁-101

由于JVM启动时,也会存在大量的同步块以及多个线程竞争场景,为了减少锁升级过程带来的开销, 会延迟启用偏向锁。

JDK 1.6之后,默认开启偏向锁

开启偏向锁: -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
关闭偏向锁: -XX:-UseBiasedLocking

// VM options: -XX:BiasedLockingStartupDelay=0
Object obj = new Object();
// 无锁状态-处于匿名偏向状态-101
// 00000101 00000000 00000000 00000000
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
// 偏向锁-101
synchronized (obj) {
    // 00000101 01110000 10000000 01111100
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}

tips:

获取到偏向锁之后,调用对象的hashCode方法时,此时会升级为轻量级锁,因为偏向锁状态时,获取不到对象的hashCode,需要升级到轻量级锁后从栈空间的LockRecord中获取。

  • 轻量级锁-00
Object obj = new Object();

new Thread(() -> {
    synchronized (obj) {
        // 偏向锁 00000101 10010000 00001010 11011100
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}).start();

try {
    Thread.sleep(2000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

new Thread(() -> {
    synchronized (obj) {
        // 轻量级锁 01001000 11111000 00110011 00001010
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}).start();
  • 重量级锁-10
Object obj = new Object();

new Thread(() -> {
    synchronized (obj) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 重量级锁 00001010 00101100 10000010 11111101
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}).start();

new Thread(() -> {
    synchronized (obj) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 重量级锁 00001010 00101100 10000010 11111101
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}).start();