对象头布局
Synchronized的锁信息放在Java对象头的MarkWord里
32位虚拟机MarkWord结构
Java Synchronized关键字原理_Synchronized

64位虚拟机MarkWord结构Java Synchronized关键字原理_反编译_02
Synchronized通过对象头中的MarkWord后三位来标识锁的类型。

Synchronized在JVM中的实现原理
重量级锁对应的锁标志位是10,存储了指向重量级监视器锁的指针,在Hotspot中,对象的监视器(monitor)锁对象由ObjectMonitor对象实现(C++),其跟同步相关的数据结构如下:

ObjectMonitor() {
  _count        = 0; //用来记录该对象被线程获取锁的次数
  _waiters      = 0;
  _recursions   = 0; //锁的重入次数
  _owner        = NULL; //指向持有ObjectMonitor对象的线程 
  _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
  _WaitSetLock  = 0 ;
  _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
}

其线程的运行过程如下图
Java Synchronized关键字原理_反编译_03
线程的生命周期存在5个状态,start、running、waiting、blocking和dead

  1. 当多个线程同时访问该方法,那么这些线程会先被放进_EntryList队列,此时线程处于blocking状态
  2. 当一个线程获取到了实例对象的监视器(monitor)锁,那么就可以进入running状态,执行方法,此时,ObjectMonitor对象的_owner指向当前线程,_count加1表示当前对象锁被一个线程获取
  3. 当running状态的线程调用wait()方法,那么当前线程释放monitor对象,进入waiting状态,ObjectMonitor对象的_owner变为null,_count减1,同时线程进入_WaitSet队列,直到有线程调用notify()方法唤醒该线程,则该线程会先进入Enterlist 然后竞争,重新获取monitor对象进入_Owner区
  4. 如果当前线程执行完毕,那么也释放monitor对象,进入terminated状态,ObjectMonitor对象的_owner变为null,_count减1

Synchronized在字节码层面实现原理

Synchronized修饰代码块
采用monitorenter,monitorexit指令,同步方法块在进入代码块时插入了monitorentry语句,在退出代码块时插入了monitorexit语句。
例如,同步代码块如下:

public class SyncCodeBlock {
   public int i;
   public void syncTask(){
       synchronized (this){
           i++;
       }
   }
}

对同步代码块编译后的class字节码文件反编译,结果如下(仅保留方法部分的反编译内容):

  public void syncTask();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter  //注意此处,进入同步方法
         4: aload_0
         5: dup
         6: getfield      #2             // Field i:I
         9: iconst_1
        10: iadd
        11: putfield      #2            // Field i:I
        14: aload_1
        15: monitorexit   //注意此处,退出同步方法
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit //注意此处,退出同步方法
        22: aload_2
        23: athrow
        24: return
      Exception table:
      //省略其他字节码.......

可以看出同步方法块在进入代码块时插入了monitorentry语句,在退出代码块时插入了monitorexit语句,为了保证不论是正常执行完毕(第15行)还是异常跳出代码块(第21行)都能执行monitorexit语句,因此会出现两句monitorexit语句。

Synchronized修饰方法块
Synchronized方法同步不再是通过插入monitorentry和monitorexit指令实现,而是由方法调用指令来读取运行时常量池中的ACC_SYNCHRONIZED标志隐式实现的,如果方法表结构(method_info Structure)中的ACC_SYNCHRONIZED标志被设置,那么线程在执行方法前会先去获取对象的monitor对象,如果获取成功则执行方法代码,执行完毕后释放monitor对象,如果monitor对象已经被其它线程获取,那么当前线程被阻塞。

例如同步方法代码如下:

public class SyncMethod {
   public int i;
   public synchronized void syncTask(){
           i++;
   }
}

对同步方法编译后的class字节码反编译,结果如下(仅保留方法部分的反编译内容):

public synchronized void syncTask();
    descriptor: ()V
    //方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field i:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field i:I
        10: return
      LineNumberTable:
        line 12: 0
        line 13: 10
}

可以看出方法开始和结束的地方都没有出现monitorentry和monitorexit指令,但是出现的ACC_SYNCHRONIZED标志位。说明当Synchronized修饰的是方法时,通过ACC_SYNCHRONIZED实现

Synchronized在汇编层面的实现原理
采用 lock cmpxchg 指令,多核CPU需要加lock前缀,非多核被替换为nop关于compxchg指令详解