public void foo(Object lock) {
    synchronized (lock) {
        lock.hashCode();
    }
}
 
    
   
  
   
   public void foo(java.lang.Object);
  descriptor: (Ljava/lang/Object;)V
  flags: ACC_PUBLIC
  Code:
    stack=2, locals=4, args_size=2
       0: aload_1
       1: dup
       2: astore_2
       3: monitorenter
       4: aload_1
       5: invokevirtual #2                  // Method java/lang/Object.hashCode:()I
       8: pop
       9: aload_2
      10: monitorexit
      11: goto          19
      14: astore_3
      15: aload_2
      16: monitorexit
      17: aload_3
      18: athrow
      19: return
    Exception table:
       from    to  target type
           4    11    14   any
          14    17    14   any
 
    
   
  
  -  monitorenter指令和monitorexit指令均会消耗操作数栈上的一个 引用类型 元素,作为所要加锁解锁的 锁对象 
-  上面的字节码包含一个monitorenter指令和多个monitorexit指令 
    
    -  JVM需要保证所获得的锁在 正常执行路径 以及 异常执行路径 上都能够被 解锁 
-  具体的执行路径请参照 Exception table 
 
实例方法 + 静态方法 
 
  
   
   public synchronized void eoo(Object lock) {
    lock.hashCode();
}
 
    
   
  
   
   public synchronized void eoo(java.lang.Object);
  descriptor: (Ljava/lang/Object;)V
  flags: ACC_PUBLIC, ACC_SYNCHRONIZED
  Code:
    stack=1, locals=2, args_size=2
       0: aload_1
       1: invokevirtual #2                  // Method java/lang/Object.hashCode:()I
       4: pop
       5: return
 
    
   
  
  -  字节码中方法的访问标记(flags)包括 ACC_SYNCHRONIZED 
- 进入该方法时,JVM需要执行monitorenter操作
- 不管正常返回还是向调用者抛出异常,JVM均需要执行monitorexit操作
-  这里的monitorenter操作和monitorexit操作 所对应的锁对象是隐式 的 
    
    -  对于 实例方法 来说,锁对象为 this 
-  对于 静态方法 来说,锁对象为 所在类的Class实例 
 
monitorenter + monitorexit 
 
  
  -  抽象理解:每个 锁对象 拥有一个 锁计数器 和 指向持有该锁的线程的指针 
-  当执行monitorenter时,如果锁对象的计数器为0 
    
    - 那么说明锁对象还没有被其他线程所持有
- JVM会将锁对象的持有线程设置为当前线程,并将计数器+1
 
-  当执行monitorenter时,如果锁对象的计数器不为0 
    
    - 如果锁对象的持有线程是当前线程,那么JVM会将其计数器+1
- 否则需要等待,直到持有该锁对象的线程释放该锁
 
-  当执行monitorexit时,JVM则需要将该锁对象的计数器-1 
    
-  采用计数器的方式,是为了允许同一个线程重复获取同一把锁, 可重入 
锁优化 
 
 对象状态图 
 
   
 
 
  针对 一个对象的整个生命周期 ,锁升级是 单向不可逆 :偏向锁 -> 轻量级锁 -> 重量级锁 
 
 重量级锁 
 
  
  -  重量级锁是JVM中最为基础的锁实现 
    
    - JVM会阻塞加锁失败的线程,并在目标锁被释放掉的时候,唤醒这些线程
-  Java线程的 阻塞 和 唤醒 ,都依赖于 操作系统 完成 
-  涉及系统调用,需要从操作系统的 用户态切换至内核态 , 开销很大 
-  每个 Java对象 都存在一个与之关联的 monitor对象 
 
-  为了尽量避免昂贵的线程阻塞和唤醒操作,采用 自旋 
    
    -  自旋:在处理器上 空跑 并且 轮询锁是否被释放 
-  线程会进入自旋的两种情况( 睡前醒后 ) 
      
      -  线程即将 进入阻塞状态之前 
-  线程 被唤醒后竞争不到锁 
 
-  与线程阻塞相比,自旋可能会 浪费大量的处理器资源 
      
      -  JVM采取的是 自适应自旋 
-  根据以往 自旋等待时间 是否能够获得锁来动态调整自旋的时间(循环次数) 
 
-  自旋还会带来 不公平锁 
      
      - 处于阻塞状态的线程,没有办法立即竞争被释放的锁
- 而处于自旋状态的线程,则很有可能优先获得这把锁
 
 
轻量级锁 
 
  
  -  场景: 多个线程在不同的时间段请求同一把锁,没有锁竞争 
-  JVM采用轻量级锁,可以避免 避免重量级锁的阻塞和唤醒 
加锁 
 
  
  -  如果锁对象标记字段的最后两位为 01 ( 无锁或偏向锁 ) 
-  JVM会在 当前栈帧 中建立一个名为 锁记录 (Lock Record)的内存空间 
    
    -  用于存储锁对象当前的标记字段(Mark Word)的拷贝,叫作 Displaced Mark Word 
 
- 拷贝锁对象的标记字段到锁记录中,见下图:轻量级锁CAS操作之前
-  JVM使用 CAS操作 
    
    -  将 锁对象的标记字段更新为指向锁记录的指针 
-  将 锁记录里面的owner指针指向锁对象 
 
-  如果CAS更新成功,那么当前线程 拥有 了该锁对象的 锁 ,并将锁对象标记字段的锁标志位设置为 00 
    
    - 此时该锁对象处于轻量级锁的状态,见下图:轻量级锁CAS操作之后
 
-  如果CAS更新失败,检查 锁对象的标记字段 是否指向 当前线程的栈帧 
    
    -  如果是,说明 锁重入 ,继续执行同步代码 
-  如果不是,说明出现多线程竞争,膨胀为 重量级锁 
      
      -  锁标记位变为 10 ,锁对象的标记字段存储的是 指向monitor对象的指针 
-  后面等待锁的线程进入阻塞状态,当前线程则尝试使用自旋来获取锁 
 
 
 
  轻量级锁CAS操作之前
 
   
 
   
  
  轻量级锁CAS操作之后
 
   
 
   
 解锁 
 
  
  -  JVM通过 CAS操作 , 把线程中的Displaced Mark Word复制回锁对象的标记字段 
- 如果CAS更新成功,同步过程结束
-  如果CAS更新失败,说明其他线程尝试获取过该锁( 现已膨胀 ),在释放锁的同时, 唤醒被挂起的线程 
偏向锁 
 
  
  -  在 无多线程竞争 的情况下,仍然需要 尽量减少不必要的轻量级锁的执行路径 
    
    -  轻量级锁的获取和释放 依赖多次CAS原子指令 
- 由此引入了偏向锁
 
-  偏向锁乐观地认为: 从始至终只有一个线程请求某一把锁 
    
    - 只需要在置换ThreadID时需要依赖一次CAS操作
-  一旦出现多线程 竞争 ,必须 撤销 偏向锁 
-  轻量级锁是为了在 线程交替执行 同步块时提高性能 
-  偏向锁是为了在 只有一个线程执行 同步块时进一步提高性能 
 
加锁 
 
  
  -  偏向锁只会在 第1次 加锁时采用CAS操作 
-  如果锁对象的标记字段最后三位为 101 ,即 可偏向状态 
-  锁对象处于 未偏向 状态(Thread ID == 0) 
    
    -  那么JVM会通过 CAS操作 ,将 当前线程的ID 记录在该 锁对象的标记字段 中 
- 如果CAS成功,则认为当前线程获得该锁对象的偏向锁
-  如果CAS失败,说明另外一个线程抢先获取了偏向锁 
      
      -  此时需要 撤销偏向锁 ,使得锁对象进入 轻量级锁 状态 
 
 
-  锁对象处于 已偏向 状态(Thread ID != 0) 
    
    -  检测: 标记字段中的线程ID == 当前线程的ID? 
-  如果是,说明 锁重入 ,直接返回 
-  如果不是,说明该锁对象目前偏向于其他线程,需要 撤销偏向锁 
-  一个线程执行完同步代码后, 不会将标记字段的线程ID清空 ,最小化开销 
 
 
        
                原文  
                http://zhongmingmao.me/2018/12/31/jvm-basic-synchronized/