从源码分析Synchronized实现原理

13 篇文章 2 订阅
1 篇文章 0 订阅

前面的简单铺垫对于懂得人看起来可能有些罗嗦,如果前面都知道怎么回事,可以直接 跳过1,2章节 往后看。

1 线程安全简介

使用Synchronized,是为了保证线程安全,那一般什么情况下会出现线程不安全的情况?通常满足一下两个条件:

  1. 多个线程操作共享资源。
  2. 对共享资源的操作会使得共享资源发生变化。

如有下面程序

public class SecurityDemo {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++){
            new Thread(new Runnable() {
                public void run() {
                    for (int j = 0; j < 1000 ; j++){
                        count ++;
                    }
                }
            }).start();
            System.out.println("i = " + i);
        }
        TimeUnit.SECONDS.sleep(1);
        System.out.println(count);
    }
}

程序中,创建了10个线程,对公共数据count进行累计,最终的到的结果 <= 10000,就是线程不安全的情况。

为了保证线程安全,我们最直接的想到的就是对线程加锁。加锁使得多个线程执行互斥,即同一时间内,只有获得锁的线程能够执行加锁的代码段。
在这里插入图片描述
加锁,第一时间想到的就是synchronized,synchronized最早是重量级锁的概念,所谓重量级锁,就是存在锁竞争的时候,一个线程获取锁之后,会将之后想要获取锁的线程挂起,而java线程和操作系统线程是一一映射的,所以对线程的挂起需要操作系统的支持,就需要进行用户态和内核态的转换,提别的消耗CPU,降低程序的执行效率。在1.6以后,对锁进行了优化。之所以优化,因为我们既要保证数据的安全性,又要保证性能。

2 synchronized的使用

synchronized使用很简单,根据修饰的位置不同,synchronized的使用分为三种情况:

2.1 修饰代码块

public class SynchronizedDemo {
    public void fun(){
        synchronized (this){
            System.out.println();
        }
    }
}

上面这种修饰代码块的方式,锁的钥匙对象通常是使用者指定的,可以是任意的对。这里使用的是this,控制是当前实例当前方法的此代码块。这种方式可以精准,灵活的控制锁的粒度。

2.2 修饰实例方法

public class SynchronizedDemo {
    public synchronized void fun(){
        System.out.println();
    }
}

这种方式,修饰实例方法,锁对象是对象实例本身this。控制范围是当前实例范围内整个方法。

3.3 修饰静态方法

public class SynchronizedDemo {
    public synchronized static void fun(){
        System.out.println();
    }
}

这种方式,锁对象是SynchronizedDemo .class对象。控制的范围就是所有SynchronizedDemo 实例对象。

上面这三种方式,由于修饰的位置不同,所以锁对象不同,最终锁能够控制的范围就不同。

修饰位置锁类型控制范围优缺点
修饰代码块对象锁当前对象可以指定锁对象,控制粒度细
修饰实例方法对象锁当前对象不能指定锁对象,作用于整个方法
修饰静态方法类锁所有由当前class创建的对象控制所有由当前class创建的对象,控制范围大

一般情况下,锁控制范围粒度越小,程序的性能会越高。但也有特殊情况,如可以把循环中的加锁加锁放于循环外,减少频繁的加锁解锁过程。

3 synchronized实现原理

synchronized使用很简单,使用的时候,只需要为synchronized指定一个锁对象,并且这个锁对象可以是任意对象,为了了解synchronized实现原理,第一个需要解决的问题就是:为什么每个对象都能够成为锁对象?

3.1 为什么每个对象都能够成为锁对象?

为了了解为什么每个对象都能够成为锁对象,我们首先要弄明白java对象在内存中的布局,即如何存储的。

3.1.1 java对象内存布局

在java程序执行的时候,对于每个java对象,在jvm中都有一个oopDesc对象与之相对应,我们可以通过oopDesc来了解java对象内存布局。 这个oopDesc定义在hotspot源码中的oop.hpp文件中,是jvm中java对象描述的基类。其有很多实现子类:

oopDesc子类描述源码中的描述
instanceOopDescJava实例对象An instanceOop is an instance of a Java Class , Evaluating “new HashTable()” will create an instanceOop.
arrayOopDesc数组基类, objArrayOopDesc、typeArrayOopDesc继承此类arrayOopDesc is the abstract baseclass for all arrays. It doesn’t declare pure virtual to enforce this because that would allocate a vtbl in each instance, which we don’t want.
objArrayOopDescjava对象数组An objArrayOop is an array containing oops, Evaluating “String arg[10]” will create an objArrayOop.
typeArrayOopDesc基本类型数组A typeArrayOop is an array containing basic types (non oop elements), It is used for arrays of {characters, singles, doubles, bytes, shorts, integers, longs}

oopDesc定义定义部分源码如下:

class oopDesc {
  friend class VMStructs;
 private:
  volatile markOop  _mark;
  union _metadata {
    Klass*      _klass;
    narrowKlass _compressed_klass;
  } _metadata;
}

oopDesc定义中,有两个字段:
_mark : 用于存储对象的 HashCode、GC分代年龄,锁标记等信息,jvm中称之为Mark Word。这个稍候详细说明。

_metadata:是一个指向 Klass(类的class信息)的指针,是一个共用体( union )。也就是说它要么是 klass 字段要么是 compressedklass 。在64位jvm中,当 JVM 开启了-XX:+UseCompressedClassPointers( 表示启用 Klass 指针压缩选项, JVM 默认开启 )选项时使用 commpressedklass 来存储 Klass 引用,此时指针只占用4字节,否则使用 _klass 存储 Klass 引用。

在数组相关的 Oop 类中,除了上述两个数据成员外,还有一个 int 类型数据成员 length ,用于表示数组的长度。具体的详见hotspot源码定义中的arrayOop.hpp文件。

oopDesc定义的就是java对象的对象头的内容,即包括Mark Word和一个执行Class原数据的指针。除了对象头内容,对象中还有实例数据,实例数据在子类中定义,如instanceOopDesc类。对于java对象,无论是从父类继承的,还是自己定义的,都需要保存起来。

下面通过一个具体的例子来简单看一下各个区域的联系,如有如下代码:

public class OopDemo {
    private static int count;
    private String name;
    public OopDemo(String name){
        this.name = name;
        count ++;
    }
    public static void main(String[] args) {
        OopDemo oopDemoA = new OopDemo("oopDemoA");
        OopDemo oopDemoB = new OopDemo("oopDemoB");
    }
}

对应的各个区域如下:
在这里插入图片描述
除了对象头和实例数据外,对象在内存中的布局还有一块为对其填充,对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或者2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

综上,java对象,在内存中的布局,大致分为三块:对象头,实例数据,对其填充。
在这里插入图片描述

3.1.2 对象头

弄明白了java对象的内存分布,会发现,在java对象中,会发现,在java对象存储中,有个对象头,对象头中有个Mark Word, Mark Word在jvm中是markoop对象,下面是前面提到的对象头定义,其中_mark就是Mark Word。

class oopDesc {
  friend class VMStructs;
 private:
  volatile markOop  _mark;
  union _metadata {
    Klass*      _klass;
    narrowKlass _compressed_klass;
  } _metadata;
}

markOop定义在hotspot源码中的markoop.hpp文件中,下图是markoop(Mark Word)的存储状态定义代码:
在这里插入图片描述
在32位操作系统中,用图表示为:
在这里插入图片描述
在markoop中定义了枚举值与之相对应:

enum { locked_value             = 0,//00 轻量级锁
         unlocked_value           = 1,//01 无锁
         monitor_value            = 2,//10 重量级锁
         marked_value             = 3,//11 GC标记
         biased_lock_pattern      = 5 //101 偏向锁
  };

讲到这里,就明白了为什么每个对象都能够成为锁,因为每个对象都有对象头,而锁标记是存储在对象头中的Mark Word中。

3.2 synchronized 初探

如有如下代码:

public class SynchronizedDemo2 {
    public int count;
    public  void fun2(){
        synchronized(this){
            count ++;
        }
    }
    public static void main(String[] args)  {
        SynchronizedDemo2 synchronizedDemo = new SynchronizedDemo2();
        synchronizedDemo.fun2();
    }
}

在编译生成class文件后,通过javap -v 命令查看class字节码文件如下:
在这里插入图片描述
如上图,在进入synchronized代码块的时候会执行monitorenter指令,在退出代码块的时候会执行monitorexit指令,编译后之所以会有两个monitorexit是因为需要在抛异常的时候释放锁。

monitotenter和monitorexit指令执行的时候具体做了什么呢?这就需要看jvm的具体实现,需要找到monitorenter和monitorexit在jvm中对应的代码一代究竟。想要找到monitorenter和monitorexit在jvm中对应的代码,就需要了解jvm的class字节码是如何执行的?

在早期的jvm中,在解释执行class指令的时候,是通过把每个class指令映射为C++代码,具体定义在jvm源码中的bytecodeInterpreter.cpp文件中,如monitorenter代码片段如下:
在这里插入图片描述
但是这种解释执行速度很慢,原因是每个class字节码指令对应一堆的C++代码,一堆的C++代码会翻译成一堆的CPU执行的指令,会存在很多冗余指令,都知道指令越多,CPU的执行速度肯定会越慢,在无法对编译器就行优化的前提下,怎样才能提高java字节码的执行速度呢?之所执行速度慢,就是经过了字节码指令转C++这一步操作导致大量的冗余代码,所以是否能够跳过这一步,直接将class对应为本地机器的机器码呢?是的,jvm团队就是这样做的?所以最早起的解释执行就被废止,而采用模板执行器,就是将每个class字节码对应为一段汇编代码,具体定义在jvm源码中的 templateTable.cpp文件中:
在这里插入图片描述
templateTable.cpp中对于指令的汇编实现,对于不同的CPU有不同的实现,如x86的在templateTable_x86.cpp以及interp_masm_x86.cpp中。汇编比较晦涩,下面的源码都看bytecodeInterpreter.cpp中的C++实现,和汇编没有本质区别。

3.3 synchronized锁的升级

在jdk1.6之前,当一个线程获得synchronized,会阻塞后面的所有其他线程,而java的线程和操作系统的线程是一一映射的,所以阻塞和唤醒线程,都需要操作系统提供支持,都需要从用户态转入内核态,需要耗费大量的CPU的时间,这种状态转换用时可能比执行用户代码的时间还要长。所以在jdk1.6之前,synchronized都是一个重量级的操作。而在jdk1.6之后,对synchronized进行的优化,引入了偏向锁和轻量级锁的概念,使得synchronized的性能得到了很大的提升。jdk1.6之后的synchronized,从偏向锁到轻量级锁,再到重量级锁,是一个锁的升级过程。下面先介绍三种锁的实现原理,再讲解升级过程。

3.3.1 偏向锁

首先要了解,为什么要引入偏向锁?
因为发现,在很多时候,对一段代码块虽然加了synchronized,但是,依然只有一个线程去访问(同一时段,不存在两个线程都要访问同步代码块的情况)。所以就引入了偏向锁。

偏向锁的偏,就是偏心,偏袒的意思,就是当一个线程获取了偏向锁后,如果在接下来的执行过程中,只要没有其他线程尝试去获取锁,则持有锁的当前线程再次进入同步代码块时不再需要进行加锁。

通过前面的讲解,我们知道了锁标记是记录在对象头中的,JVM可以通过参数 -XX:+UseBiasedLocking 启用偏向锁,在jdk1.6以后,默认是开启的。所以在通过new创建对象为对象分配内存的时候,也会判断JVM是否启用的偏向锁而对对象头的Mark Word进行初始化,具体在bytecodeInterpreter.cpp中,代码如下。

CASE(_new): {
     	......
     	......
       //判断是否启用偏向锁对对象头进行初始化
       if (UseBiasedLocking) {
         result->set_mark(ik->prototype_header());
       } else {
         result->set_mark(markOopDesc::prototype());
       }
       	......
     	......       
}

如果启用了偏向锁,对象初始化的时候,就会初始化对象头 Mark Word为:
在这里插入图片描述

3.3.1.1偏向锁的获取流程

代码就在bytecodeInterpreter.cpp定义的monitorenter中:

CASE(_monitorenter): {
        oop lockee = STACK_OBJECT(-1);
        // derefing's lockee ought to provoke implicit null check
        CHECK_NULL(lockee);
        // find a free monitor or one already allocated for this object
        // if we find a matching object then we need a new monitor
        // since this is recursive enter
        BasicObjectLock* limit = istate->monitor_base();
        BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
        BasicObjectLock* entry = NULL;
        //从当前线程栈中获取一个空闲的BasicObjectLock
        while (most_recent != limit ) {
          if (most_recent->obj() == NULL) entry = most_recent;
          else if (most_recent->obj() == lockee) break;
          most_recent++;
        }
        //获取BasicObjectLock成功
        if (entry != NULL) {
          //将线程栈中的BasicObjectLock的obj指针指向锁对象
          entry->set_obj(lockee);
          int success = false;
          uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;

          markOop mark = lockee->mark();
          intptr_t hash = (intptr_t) markOopDesc::no_hash;
          // implies UseBiasedLocking
          //判断是否是是偏向模式
          if (mark->has_bias_pattern()) {
            uintptr_t thread_ident;
            uintptr_t anticipated_bias_locking_value;
            thread_ident = (uintptr_t)istate->thread();
            anticipated_bias_locking_value =
              (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &
              ~((uintptr_t) markOopDesc::age_mask_in_place);
            //已经偏向,并且偏向的就是自己,什么都不用做
            if  (anticipated_bias_locking_value == 0) {
              // already biased towards this thread, nothing to do
              if (PrintBiasedLockingStatistics) {
                //如果需要统计偏向锁重入次数,可以使用biased_lock_entry_count_addr统计
                (* BiasedLocking::biased_lock_entry_count_addr())++;
              }
              success = true;
            }
            //对象的Klass的对象头不是偏向模式,则撤销偏向
            else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
              // try revoke bias
              markOop header = lockee->klass()->prototype_header();
              if (hash != markOopDesc::no_hash) {
                header = header->copy_set_hash(hash);
              }
              if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) {
                if (PrintBiasedLockingStatistics)
                  (*BiasedLocking::revoked_lock_entry_count_addr())++;
              }
            }
            //当前对象的对象头epoch不等于Klass中的epoch,则尝试重新偏向
            else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
              // try rebias
              markOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident);
              if (hash != markOopDesc::no_hash) {
                new_header = new_header->copy_set_hash(hash);
              }
              //进行CAS将对象头中线程Id替换为自己,如果替换成功,则偏向成功
              if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), mark) == mark) {
                if (PrintBiasedLockingStatistics)
                  (* BiasedLocking::rebiased_lock_entry_count_addr())++;
              }
              //替换失败,则开始锁的升级
              else {
                CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
              }
              success = true;
            }
            else {
              //重新偏向, 如果markOop是匿名偏向的时候会偏向当前线程成功
              // try to bias towards thread in case object is anonymously biased
              markOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place |
                                                              (uintptr_t)markOopDesc::age_mask_in_place |
                                                              epoch_mask_in_place));
              if (hash != markOopDesc::no_hash) {
                header = header->copy_set_hash(hash);
              }
              markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
              // debugging hint
              DEBUG_ONLY(entry->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);)
              //进行CAS替换,如果替换成功,则偏向成功(只有markOop是匿名偏向的时候,才会替换成功)
              if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
                if (PrintBiasedLockingStatistics)
                  (* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
              }
              //失败,则升级
              else {
                CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
              }
              success = true;
            }
          }

          // traditional lightweight locking
          //轻量级锁
          if (!success) {
            //设置当前的对象头为无锁状态,并且复制到Lock Record中的markoop中
            markOop displaced = lockee->mark()->set_unlocked();
            entry->lock()->set_displaced_header(displaced);
            bool call_vm = UseHeavyMonitors;
            //将对象头中的地址替换为指向Lock Record的指针,替换成功,则说明获取轻量级锁成功,则什么都不做。
            //这里判断,如果是替换失败,则继续判断是否是锁重入。
            if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
              // Is it simple recursive case?
              //这里判断是不是锁重入,判断指向Lock Record的指针指向的地址是否属于当前线程栈
              if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
                //如果是轻量级锁的锁重入,说明前面set_displaced_header设置的是第一个Lock Record的地址,
                //所以要重新将申请的Lock Record的displaced_header置为空,同样也会通过申请的displaced_header的个数来统计轻量级锁的重入次数
                //栈的最高位的Lock Record的displaced_header不是空,重入锁退出锁的时候,会由低到高遍历退出,只在最后一个锁的时候使用CAS替换
                entry->lock()->set_displaced_header(NULL);
              } else {
                //替换失败,则进行锁升级
                CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
              }
            }
          }
          UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
        } else {
          istate->set_msg(more_monitors);
          UPDATE_PC_AND_RETURN(0); // Re-execute
        }
      }
  1. 从当前线程栈中找到一个空闲的Lock Record(Lock Record是BasicObjectLock对象),判断Lock Record是否空闲的依据是其obj字段 是否为null。
    BasicObjectLock定义为:
class BasicObjectLock VALUE_OBJ_CLASS_SPEC {
  friend class VMStructs;
 private:
  BasicLock _lock;                                    // the lock, must be double word aligned
  oop       _obj;                                     // object holds the lock;
}

可以看到有两部分,BasicLock对象和Oop。oop是用来指向锁对象的,BasicLock是什么呢?BasicLock定义如下

class BasicLock VALUE_OBJ_CLASS_SPEC {
  friend class VMStructs;
  friend class JVMCIVMStructs;
 private:
  volatile markOop _displaced_header;
  }

可以看到,BasicLock中就是一个markOop对象,这个就是我们前面提到的对象头,这个干什么用呢,在后面讲到轻量级锁的时候,你就会了解到。
从线程栈中申请到Lock Record后,情况是这样的:
在这里插入图片描述

  1. 为Lock Record中的obj赋值,将Lock Record中的obj指针指向锁对象。
    在这里插入图片描述

  2. 判断,当前锁对象是否是偏向模式,如果是,则开始执行获取偏向锁的流程。不是,则执行获取轻量级锁的流程。

  3. 如果是偏向模式,则当前线程开始获取偏向锁,里面的流程逻辑比较复杂,这里简单归纳就是:
    (1)判断当前线程已经偏向,并且偏向的线程是自己,则什么都不做。
    (2)如果当前锁对象的Klass的对象头不是偏向模式(可能对象头已经锁升级),则尝试撤销当前对象的偏向锁。
    (3)如果当前现成从来没有偏向过任何现成,则尝试通过CAS将偏向线程改为当前线程,替换成功,则偏向成功。否则执行InterpreterRuntime::monitorenter进行锁升级。如果CAS修改成功,说明当前线程T1获取当前对象偏向锁成功,情况如下:
    在这里插入图片描述

  4. 这里需要提的是,如果不是偏向模式,或者在第二步中撤销了偏向锁,就进入的轻量级锁获取的逻辑。轻量级锁的获取,在后面会详细讲到。

这里需要注意的是,如果当前持有偏向锁的线程出现的锁重入,情况是如何呢?
根据上面的将的流程最终形成的情况如下,并且在锁重入过程中,并没有使用CAS。
在这里插入图片描述
整个获取流程如下:
在这里插入图片描述

说完了偏向锁的获取流程,下面看一下偏向锁的退出流程(注意这里是退出,不是撤销):

3.3.1.2偏向锁的退出流程

注意这里是退出,是指执行monitorexit, 不是撤销。

CASE(_monitorexit): {
        oop lockee = STACK_OBJECT(-1);
        CHECK_NULL(lockee);
        // derefing's lockee ought to provoke implicit null check
        // find our monitor slot
        BasicObjectLock* limit = istate->monitor_base();
        BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
        //循环遍历线程栈中的Lock Record
        while (most_recent != limit ) {
           //如果Lock Record的Obj指向的是当前锁对象,说明是当前锁对象的Lock Record
          if ((most_recent)->obj() == lockee) {
            BasicLock* lock = most_recent->lock();
            markOop header = lock->displaced_header();
            //将obj设置为Null
            most_recent->set_obj(NULL);
            //如果不是偏向模式(即是轻量级锁)
            if (!lockee->mark()->has_bias_pattern()) {
              bool call_vm = UseHeavyMonitors;
              // If it isn't recursive we either must swap old header or call the runtime
              if (header != NULL || call_vm) {
                //将对象头中的markoop替换为Lock Record中的markoop
                if (call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {
                  // restore object for the slow case
                  
                  //如果替换失败,则还原Lock Record,并且执行锁升级的monitorexit
                  most_recent->set_obj(lockee);
                  CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);
                }
              }
            }
            UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
          }
          most_recent++;
        }
        // Need to throw illegal monitor state exception
        CALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD), handle_exception);
        ShouldNotReachHere();
      }

上面其实是偏向锁以及轻量级锁的的退出逻辑:

  1. 遍历当前线程栈中的所有的Lock Record, 如果Lock Record的obj指向的是自己,则说明当前的Lock Record属于当前锁对象。
  2. 将Lock Record的obj设置为空。如果是偏向锁,则就不做其他操作了。
  3. 如果是轻量级锁,设置为空后,还需要通过CAS将Lock Record中的mark oop替换回对象头中的markoop,如果替换失败,则需要恢复Lock Record,并执行锁升级的逻辑InterpreterRuntime::monitorexit,轻量级锁后面详细说明。

这里需要注意两个点:

  1. 这里使用的是循环,之所以循环,就是存在锁重入的情况,需要把每个Lock Record的obj都置为空。
  2. 退出,仅仅是把Lock Record的obj置为空,并没有对象头中的线程ID替换掉。所以当线程T1退出的时候,情况如下:
    在这里插入图片描述
3.3.1.3偏向锁的撤销流程

在这里,如果获取偏向锁失败了,如在上面的情况中,线程1获取了偏向锁,对象头中存放的是线程1的线程ID,此时,线程2直行到同步代码块,开始执行获取锁的流程,因为对象头中存放的是线程1的ID,所以线程2再通过CAS将对象头替换成线程2的ID的时候,肯定会替换失败,此时就会执行InterpreterRuntime::monitorenter进行偏向锁的撤销。撤销根据不同的情况,可能会重新偏向,可能会撤销成为无锁状态,也可能升级为轻量级锁。撤销流程首先执行InterpreterRuntime::monitorenter,代码如下:

if (UseBiasedLocking) {
    // Retry fast entry if bias is revoked to avoid unnecessary inflation
    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
  } else {
    ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
  }
}

这里判断是否启用了偏向锁,这个是jvm参数设置的,前面有讲过,启用了则执行fast_enter,fast_enter是偏向锁的撤销,slow_enter是轻量级锁的升级,轻量级锁在后面详细说明。fast_enter定义在synchronizer.cpp中,这里注意,传入的第三个参数attempt_rebias(尝试重新偏向为true),后面会经常用来做判断:
这里是在看偏向锁的撤销,所以是看fast_enter的实现:

void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock,
                                    bool attempt_rebias, TRAPS) {
  if (UseBiasedLocking) {
  	//如果不在安全点触发的撤销,会执行这个
    if (!SafepointSynchronize::is_at_safepoint()) {
      BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
      if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
        return;
      }
    } else {
    //说明是安全点触发的撤销,这种撤销是jvm发起的
      assert(!attempt_rebias, "can not rebias toward VM thread");
      BiasedLocking::revoke_at_safepoint(obj);
    }
    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
  }

  slow_enter(obj, lock, THREAD);
}

这里判断了是不是在安全点触发的撤销,在安全点发起的撤销是有JVM发起的,为什么会有这样的判断呢?
首先,要撤销偏向锁,一定不能够影响程序本身的执行。有以下三种情况:

  1. 如果当前对象偏向锁并没有偏向任何线程,这样不管在任何时间撤销都是没有影响的。
  2. 如果是当前线程持有对象锁,然后当前线程撤销自己持有的对象锁的偏向,也是没有问题的。
  3. 如果线程1获取了偏向锁,当线程2来获取锁时,发现偏向锁被线程1持有,此时要发起偏向锁的撤销,因为锁已经被线程1持有,线程2不能立即撤销偏向锁,因为会影响线程1。所以此时撤销不能由线程2发起,就会将撤销的任务交给JVM,JVM会在安全点(安全点相关知识自行谷歌)发起偏向锁的撤销,会在下面看到这个逻辑。

根据我们的分析执行到这里,说明就是线程1获取了偏向锁,而线程2尝试执行撤销的情况,这种情况不是JVM引起的,所以继续执行BiasedLocking::revoke_and_rebias。BiasedLocking::revoke_and_rebias定义在biasedLocking.cpp中,如下:

BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {
  assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");

  // We can revoke the biases of anonymously-biased objects
  // efficiently enough that we should not cause these revocations to
  // update the heuristics because doing so may cause unwanted bulk
  // revocations (which are expensive) to occur.
  markOop mark = obj->mark();
  //如果不允许重新偏向,则执行
  if (mark->is_biased_anonymously() && !attempt_rebias) {
    // We are probably trying to revoke the bias of this object due to
    // an identity hash code computation. Try to revoke the bias
    // without a safepoint. This is possible if we can successfully
    // compare-and-exchange an unbiased header into the mark word of
    // the object, meaning that no other thread has raced to acquire
    // the bias of the object.
    markOop biased_value       = mark;
    markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
    markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
    if (res_mark == biased_value) {
      return BIAS_REVOKED;
    }
  } else if (mark->has_bias_pattern()) {
    Klass* k = obj->klass();
    //获取Klass的markOop
    markOop prototype_header = k->prototype_header();
    //如果Klass的Oop不是偏向模式则执行
    if (!prototype_header->has_bias_pattern()) {
      // This object has a stale bias from before the bulk revocation
      // for this data type occurred. It's pointless to update the
      // heuristics at this point so simply update the header with a
      // CAS. If we fail this race, the object's bias has been revoked
      // by another thread so we simply return and let the caller deal
      // with it.
      markOop biased_value       = mark;
      markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark);
      assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked");
      return BIAS_REVOKED;
      //如果Klass markOop和锁对象的markOop的epoch不同,则执行
    } else if (prototype_header->bias_epoch() != mark->bias_epoch()) {
      // The epoch of this biasing has expired indicating that the
      // object is effectively unbiased. Depending on whether we need
      // to rebias or revoke the bias of this object we can do it
      // efficiently enough with a CAS that we shouldn't update the
      // heuristics. This is normally done in the assembly code but we
      // can reach this point due to various points in the runtime
      // needing to revoke biases.
      if (attempt_rebias) {
        assert(THREAD->is_Java_thread(), "");
        markOop biased_value       = mark;
        markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());
        markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark);
        if (res_mark == biased_value) {
          return BIAS_REVOKED_AND_REBIASED;
        }
      } else {
        markOop biased_value       = mark;
        markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
        markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
        if (res_mark == biased_value) {
          return BIAS_REVOKED;
        }
      }
    }
  }

  HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);
  if (heuristics == HR_NOT_BIASED) {
    return NOT_BIASED;
  } else if (heuristics == HR_SINGLE_REVOKE) {
    //开始撤销
    Klass *k = obj->klass();
    markOop prototype_header = k->prototype_header();
    //当前对象偏向当前线程,并且对象的epoch和Klass的Epoch相同,则直接开始执行撤销
    if (mark->biased_locker() == THREAD &&
        prototype_header->bias_epoch() == mark->bias_epoch()) {
      // A thread is trying to revoke the bias of an object biased
      // toward it, again likely due to an identity hash code
      // computation. We can again avoid a safepoint in this case
      // since we are only going to walk our own stack. There are no
      // races with revocations occurring in other threads because we
      // reach no safepoints in the revocation path.
      // Also check the epoch because even if threads match, another thread
      // can come in with a CAS to steal the bias of an object that has a
      // stale epoch.
      ResourceMark rm;
      if (TraceBiasedLocking) {
        tty->print_cr("Revoking bias by walking my own stack:");
      }
      BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD);
      ((JavaThread*) THREAD)->set_cached_monitor_info(NULL);
      assert(cond == BIAS_REVOKED, "why not?");
      return cond;
    } else {
      //如果需要撤销的不是当前线程,则需要等待线程安全点,之后Jvm在线程安全点会触发撤销程序
      VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);
      VMThread::execute(&revoke);
      return revoke.status_code();
    }
  }

  assert((heuristics == HR_BULK_REVOKE) ||
         (heuristics == HR_BULK_REBIAS), "?");
  VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,
                                (heuristics == HR_BULK_REBIAS),
                                attempt_rebias);
  VMThread::execute(&bulk_revoke);
  return bulk_revoke.status_code();
}

参数attempt_rebias是说明是否允许尝试重新偏向,从外面传入的是true,然后,根据代码中我标明的注释,可以看出前面的代码都是不会执行的,在最后开始执行撤销,执行撤销前,先判断了如果当前锁对象偏向当前线程,则直接执行撤销。如果不是,则将通过Vm在安全点的时候执行撤销程序。这里也就是前面分析的,线程1获取了锁,而线程2尝试去撤销,此时就会将撤销交给jvm。

如果你去跟交给JVM执行的撤销逻辑,你会发现,最终调用的还是revoke_bias。只是入参会有变化,所以我们这里直接看revoke_bias的逻辑:

static BiasedLocking::Condition revoke_bias(oop obj, bool allow_rebias, bool is_bulk, JavaThread* requesting_thread) {
  markOop mark = obj->mark();
  //不是偏向模式时执行
  if (!mark->has_bias_pattern()) {
    if (TraceBiasedLocking) {
      ResourceMark rm;
      tty->print_cr("  (Skipping revocation of object of type %s because it's no longer biased)",
                    obj->klass()->external_name());
    }
    return BiasedLocking::NOT_BIASED;
  }

  uint age = mark->age();

  //偏向锁的原始markoop
  markOop   biased_prototype = markOopDesc::biased_locking_prototype()->set_age(age);
  //无锁的原始markoop
  markOop unbiased_prototype = markOopDesc::prototype()->set_age(age);

  if (TraceBiasedLocking && (Verbose || !is_bulk)) {
    ResourceMark rm;
    tty->print_cr("Revoking bias of object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s , prototype header " INTPTR_FORMAT " , allow rebias %d , requesting thread " INTPTR_FORMAT,
                  p2i((void *)obj), (intptr_t) mark, obj->klass()->external_name(), (intptr_t) obj->klass()->prototype_header(), (allow_rebias ? 1 : 0), (intptr_t) requesting_thread);
  }

  JavaThread* biased_thread = mark->biased_locker();
  //如果当前锁对象中的线程id为空
  if (biased_thread == NULL) {
    // Object is anonymously biased. We can get here if, for
    // example, we revoke the bias due to an identity hash code
    // being computed for an object.
    //如果不允许偏向,则设置对象头的markoop为无锁状态
    if (!allow_rebias) {
      obj->set_mark(unbiased_prototype);
    }
    if (TraceBiasedLocking && (Verbose || !is_bulk)) {
      tty->print_cr("  Revoked bias of anonymously-biased object");
    }
    return BiasedLocking::BIAS_REVOKED;
  }

  // Handle case where the thread toward which the object was biased has exited
  bool thread_is_alive = false;
  if (requesting_thread == biased_thread) {
    thread_is_alive = true;
  } else {
    for (JavaThread* cur_thread = Threads::first(); cur_thread != NULL; cur_thread = cur_thread->next()) {
      if (cur_thread == biased_thread) {
        thread_is_alive = true;
        break;
      }
    }
  }
  //如果对象头中对应的线程没有存活了
  if (!thread_is_alive) {
    if (allow_rebias) {
    //如果允许偏向,则设置markoop为偏向锁未偏向状态
      obj->set_mark(biased_prototype);
    } else {
     //如果不允许偏向,则设置markoop为无锁状态
      obj->set_mark(unbiased_prototype);
    }
    if (TraceBiasedLocking && (Verbose || !is_bulk)) {
      tty->print_cr("  Revoked bias of object biased toward dead thread");
    }
    return BiasedLocking::BIAS_REVOKED;
  }

  // Thread owning bias is alive.
  // Check to see whether it currently owns the lock and, if so,
  // write down the needed displaced headers to the thread's stack.
  // Otherwise, restore the object's header either to the unlocked
  // or unbiased state.
  GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread);
  BasicLock* highest_lock = NULL;
  //如果线程还存活,则需要遍历线程栈中的所有Lock Record,找到当前线程对应的Lock Record,判断持有锁的线程是否已经退出
  for (int i = 0; i < cached_monitor_info->length(); i++) {
    MonitorInfo* mon_info = cached_monitor_info->at(i);
    //如果当前的的Lock Record是当前对象
    if (mon_info->owner() == obj) {
      if (TraceBiasedLocking && Verbose) {
        tty->print_cr("   mon_info->owner (" PTR_FORMAT ") == obj (" PTR_FORMAT ")",
                      p2i((void *) mon_info->owner()),
                      p2i((void *) obj));
      }
      // Assume recursive case and fix up highest lock later
      markOop mark = markOopDesc::encode((BasicLock*) NULL);
      highest_lock = mon_info->lock();
      //设置Lock Record的markoop为NULL
      highest_lock->set_displaced_header(mark);
    } else {
      if (TraceBiasedLocking && Verbose) {
        tty->print_cr("   mon_info->owner (" PTR_FORMAT ") != obj (" PTR_FORMAT ")",
                      p2i((void *) mon_info->owner()),
                      p2i((void *) obj));
      }
    }
  }
  //如果线程栈中还存在当前对象头的Lock Record,则说明还在同步代码块中
  if (highest_lock != NULL) {
    // Fix up highest lock to contain displaced header and point
    // object at it
    //设置Lock Record的markoop为无锁
    highest_lock->set_displaced_header(unbiased_prototype);
    // Reset object header to point to displaced mark.
    // Must release storing the lock address for platforms without TSO
    // ordering (e.g. ppc).
    //设置对象头中的指针指向Lock Record,升级为轻量级锁,这里不用设置Lock Record的obj执行对象头,因为本身已经指向了
    obj->release_set_mark(markOopDesc::encode(highest_lock));
    assert(!obj->mark()->has_bias_pattern(), "illegal mark state: stack lock used bias bit");
    if (TraceBiasedLocking && (Verbose || !is_bulk)) {
      tty->print_cr("  Revoked bias of currently-locked object");
    }

    //如果已经不再当前线程中了,说明当前线程已经退出了锁
  } else {
    if (TraceBiasedLocking && (Verbose || !is_bulk)) {
      tty->print_cr("  Revoked bias of currently-unlocked object");
    }
    if (allow_rebias) {
    //允许重新偏向,则设置线程的状态为偏向锁
      obj->set_mark(biased_prototype);
    } else {
      // Store the unlocked value into the object's header.
      //允不许重新偏向,则设置线程的状态为无锁锁
      obj->set_mark(unbiased_prototype);
    }
  }

  return BiasedLocking::BIAS_REVOKED;
}

上面的撤销逻辑看似比较长,其实核心逻辑并不复杂:

  1. 如果当前对象锁已经不是偏向模式了,就不用执行撤销。
  2. 如果当前锁对象偏向的线程Id为NULL,也就是没有偏向任何线程,就根据参数allow_rebias判断是否允许重新偏向,不允许就设置锁状态为无锁,相当于撤销偏向。
  3. 判断当前锁对象中偏向的线程是否存活,如果持有偏向锁的线程已经死掉了,那如果允许重新偏向就设置对象头锁状态为偏向锁的初始状态,不允许就设置为无锁状态。
  4. 如果线程还存活,就开始执行真正的撤销了:
    这里回忆一下前面偏向锁的获取和退出流程:偏向锁的获取,就是在当前线程的线程栈中申请一块Lock Record,然后将Lock Record的obj指向锁对象,并且在对象头中存储当前线程的线程Id。而偏向锁的退出,仅仅将Lock Record中的obj值置为空,其他的什么都没有做。
    如果持有偏向锁的线程依旧存活,这里就有两种情况
    (1)持有偏向锁的线程还没有退出同步代码块
    (2)第二是持有偏向锁的线程退出了同步代码块。
    而判断是否已经退出,判断依据就是线程栈中是否还有指向锁对象的Lock Record,这以上面的代码中,首先就是遍历线程栈,判断持有锁的线程是否退出了。
    遍历结束后,如果highest_lock不等于空,说还没有退出,如果等于NULL说明已经退出了。
    如果还在代码块中没有退出,就需要升级锁为轻量级锁,升级为轻量级锁业很简单,先将Lock Record的dispatcher_header设置为无锁的markoop,在把锁对象头替换成指向LockRecord的指针。后面看完轻量级锁,再回过头看这里的升级过程,就会明白了。
    如果持有偏向锁的线程已经退出了,则判断是否允许重新偏西,如果允许重新偏向,就设置锁对象的对象头为偏向锁的初始状态。否则设置为无锁状态,即撤销偏向锁。

偏向锁撤销流程如下,这里只看最终revoke_bias的流程:
在这里插入图片描述

3.3.1.4 偏向锁与HashCode

偏向锁到这里就讲完了,但是不知道大家有没有疑问,如果JVM配置允许使用偏向锁,那原对象头中存储HashCode的部分,就要用来存储偏向的线程ID,那原对象的HashCode被存储到哪儿去了?直接覆盖肯定是不合理的。
答案是,如果一旦对象生成了HashCode,就不能在使用对象的偏向锁了。为什么这样说,可以直接看HashCode的源码,hashCode是本地native方法,实现最终调用是synchronizer.cpp中的ObjectSynchronizer::FastHashCode,具体如下:

intptr_t ObjectSynchronizer::FastHashCode(Thread * Self, oop obj) {
  if (UseBiasedLocking) {
    // NOTE: many places throughout the JVM do not expect a safepoint
    // to be taken here, in particular most operations on perm gen
    // objects. However, we only ever bias Java instances and all of
    // the call sites of identity_hash that might revoke biases have
    // been checked to make sure they can handle a safepoint. The
    // added check of the bias pattern is to avoid useless calls to
    // thread-local storage.
    if (obj->mark()->has_bias_pattern()) {
      // Handle for oop obj in case of STW safepoint
      Handle hobj(Self, obj);
      // Relaxing assertion for bug 6320749.
      assert(Universe::verify_in_progress() ||
             !SafepointSynchronize::is_at_safepoint(),
             "biases should not be seen by VM thread here");
      //首先就判断了JVM是否启用了偏向锁(这个由参数配置),然后判断当前是否是偏向模式(即偏向还没有被撤销),
      //如果都是,则撤销偏向模式
      BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());
      obj = hobj();
      assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
    }
  }

  // hashCode() is a heap mutator ...
  // Relaxing assertion for bug 6320749.
  assert(Universe::verify_in_progress() || DumpSharedSpaces ||
         !SafepointSynchronize::is_at_safepoint(), "invariant");
  assert(Universe::verify_in_progress() || DumpSharedSpaces ||
         Self->is_Java_thread() , "invariant");
  assert(Universe::verify_in_progress() || DumpSharedSpaces ||
         ((JavaThread *)Self)->thread_state() != _thread_blocked, "invariant");

  ObjectMonitor* monitor = NULL;
  markOop temp, test;
  intptr_t hash;
  markOop mark = ReadStableMark(obj);

  // object should remain ineligible for biased locking
  assert(!mark->has_bias_pattern(), "invariant");

    //无锁状态
  if (mark->is_neutral()) {
    hash = mark->hash();              // this is a normal header
    if (hash) {                       // if it has hash, just return it
      return hash;
    }
    hash = get_next_hash(Self, obj);  // allocate a new hash code
    temp = mark->copy_set_hash(hash); // merge the hash code into header
    // use (machine word version) atomic operation to install the hash
    test = (markOop) Atomic::cmpxchg_ptr(temp, obj->mark_addr(), mark);
    if (test == mark) {
      return hash;
    }
    // If atomic operation failed, we must inflate the header
    // into heavy weight monitor. We could add more code here
    // for fast path, but it does not worth the complexity.
  //这种是重量级锁的情况下,hash存储在monitor中
  } else if (mark->has_monitor()) {
    monitor = mark->monitor();
    temp = monitor->header();
    assert(temp->is_neutral(), "invariant");
    hash = temp->hash();
    if (hash) {
      return hash;
    }
    // Skip to the following code to reduce code size

    //如果是轻量级锁
  } else if (Self->is_lock_owned((address)mark->locker())) {
    temp = mark->displaced_mark_helper(); // this is a lightweight monitor owned
    assert(temp->is_neutral(), "invariant");
    hash = temp->hash();              // by current thread, check if the displaced
    if (hash) {                       // header contains hash code
      return hash;
    }
    // WARNING:
    //   The displaced header is strictly immutable.
    // It can NOT be changed in ANY cases. So we have
    // to inflate the header into heavyweight monitor
    // even the current thread owns the lock. The reason
    // is the BasicLock (stack slot) will be asynchronously
    // read by other threads during the inflate() function.
    // Any change to stack may not propagate to other threads
    // correctly.
  }

  // Inflate the monitor to set hash code
  // 到这儿,说明没有获取到hashCode,首先直接将锁膨胀为轻量级锁,然后获取hashcode并且设置hsahcode
  monitor = ObjectSynchronizer::inflate(Self, obj);
  // Load displaced header and check it has hash code
  mark = monitor->header();
  assert(mark->is_neutral(), "invariant");
  hash = mark->hash();
  if (hash == 0) {
    hash = get_next_hash(Self, obj);
    temp = mark->copy_set_hash(hash); // merge hash code into header
    assert(temp->is_neutral(), "invariant");
    test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);
    if (test != mark) {
      // The only update to the header in the monitor (outside GC)
      // is install the hash code. If someone add new usage of
      // displaced header, please update this code
      hash = test->hash();
      assert(test->is_neutral(), "invariant");
      assert(hash != 0, "Trivial unexpected object/monitor header usage.");
    }
  }
  // We finally get the hash
  return hash;
}

从实现中可以看出,执行hashCode,第一件要做的事情就是:判断了JVM是否启用了偏向锁(这个由参数配置),然后判断当前是否是偏向模式(即偏向还没有被撤销),如果都是,则撤销偏向模式。

从上面的代码中也可以看出,对象的HashCode并不是在对象的创建的时候就生成了,而是只有在调用HashCode的时候才生成,并且,一旦生成了HashCode,就会存储在对象头中,不会再次生成,至于后面的轻量级锁,重量级锁,虽然也会覆盖对象头,但是都会把对象头中的内容copy一份存储在特定的地方,后面 可以详细看到。

请注意,这里讨论的hash code都只针对调用了JVM的hashCode,是未被重写的 hashCode() 。如果用户重写了hashCode()方法,则不影响。

3.3.2 轻量级锁

为什么要引入轻量级锁?
在大部分情况下,对一段代码块加了synchronized,但多个线程之间都是交替去访问同步代码块,而非同一个线程长时间占用锁。

3.3.2.1 轻量级锁的获取过程

轻量级锁的获取代码和偏向锁的代码再一起,在对象不是偏向模式情况下,会执行获取轻量级锁的逻辑,具体如下,为了方便起见,我删除了偏向锁的逻辑。

CASE(_monitorenter): {
        oop lockee = STACK_OBJECT(-1);
        // derefing's lockee ought to provoke implicit null check
        CHECK_NULL(lockee);
        // find a free monitor or one already allocated for this object
        // if we find a matching object then we need a new monitor
        // since this is recursive enter
        BasicObjectLock* limit = istate->monitor_base();
        BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
        BasicObjectLock* entry = NULL;
        //获取一个空闲的BasicObjectLock
        while (most_recent != limit ) {
          if (most_recent->obj() == NULL) entry = most_recent;
          else if (most_recent->obj() == lockee) break;
          most_recent++;
        }
        //获取BasicObjectLock成功
        if (entry != NULL) {
          //将obj指针指向锁对象
          entry->set_obj(lockee);
          int success = false;
          uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;

          markOop mark = lockee->mark();
          intptr_t hash = (intptr_t) markOopDesc::no_hash;
          // implies UseBiasedLocking
          //判断是否是是偏向模式
          if (mark->has_bias_pattern()) {
          		//执行偏向锁的逻辑
          		......
          		......
           }
          // traditional lightweight locking
          //轻量级锁
          if (!success) {
            //设置当前的对象头为无锁状态,并且复制到Lock Record中的markoop中
            markOop displaced = lockee->mark()->set_unlocked();
            entry->lock()->set_displaced_header(displaced);
            bool call_vm = UseHeavyMonitors;
            //将对象头中的地址替换为执行Lock Record的指针,替换成功,则说明获取轻量级锁成功
            if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
              // Is it simple recursive case?
             //这里判断是不是锁重入,
              if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
                //如果是轻量级锁的锁重入,说明前面set_displaced_header设置的是第一个Lock Record的地址,
                //所以要重新将申请的Lock Record的displaced_header置为空
                entry->lock()->set_displaced_header(NULL);
              } else {
                //替换失败,则进行锁升级
                CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
              }
            }
          }
          UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
        } else {
          istate->set_msg(more_monitors);
          UPDATE_PC_AND_RETURN(0); // Re-execute
        }
      }
      UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
 }

如上面代码可以看到,偏向锁的获取流程如下:

  1. 首先在当前线程栈中申请Lock Record,将Lock Record中的obj指向锁对象。这个和偏向锁都要做的事情。
    在这里插入图片描述
  2. 取出当前锁对象的markoop,并设置其锁状态为无锁,并将其存储在Lock Record中的displaced header中。这里之所以设置为无锁,因为当轻量级锁释放退出的时候,需要将displaced header替换回对象头中,轻量级锁释放退出后,对象头的状态就是无锁状态,所以这里直接设置成无锁。
    在这里插入图片描述
  3. 通过CAS将锁对象对象头替换成指向Lock Record的指针,替换成功后,情况如下:

在这里插入图片描述

替换成功后,说明当前线程获取轻量级锁成功了。对象头的状态如下:
在这里插入图片描述
4. 如果替换失败,先判断一下是否是轻量级锁重入,如果是,说明之前已经申请过Lock Record了,就需要把新申请的Lock Record的displaced header置为空,因为一旦是重入,第二个Lock Record的displaced header存储的会是第一个Lock Record的地址。并且,存储锁对象的markoop也是为了避免覆盖保存一份,不需要保存多份。所以是重入,只是重新申请了新的Lock Record并且将obj指向对象头。用申请的lock
Record的个数来记录重入的次数。
在这里插入图片描述
5. 如果替换失败了,并且不是锁重入,就执行InterpreterRuntime::monitorenter进行锁升级。

轻量级锁的获取流程如下:
在这里插入图片描述

3.3.2.2 轻量级锁的释放过程

这里的释放,即退出synchronized。

CASE(_monitorexit): {
        oop lockee = STACK_OBJECT(-1);
        CHECK_NULL(lockee);
        // derefing's lockee ought to provoke implicit null check
        // find our monitor slot
        BasicObjectLock* limit = istate->monitor_base();
        BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
        //循环遍历线程栈中的Lock Record
        while (most_recent != limit ) {
           //如果Lock Record的Obj指向的是当前锁对象,说明是当前锁对象的Lock Record
          if ((most_recent)->obj() == lockee) {
            BasicLock* lock = most_recent->lock();
            markOop header = lock->displaced_header();
            //将obj设置为Null
            most_recent->set_obj(NULL);
            //如果不是偏向模式(即是轻量级锁)
            if (!lockee->mark()->has_bias_pattern()) {
              bool call_vm = UseHeavyMonitors;
              // If it isn't recursive we either must swap old header or call the runtime
              //这里退出锁的逻辑和获取锁是对应的,前面发现是重入,已经将displaced_header置为空了,所以这里就什么都不需要做了。
              if (header != NULL || call_vm) {
                //将对象头中的markoop替换为Lock Record中的markoop
                if (call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {
                  // restore object for the slow case

                  //如果替换失败,则还原Lock Record,并且执行锁升级的monitorexit
                  most_recent->set_obj(lockee);
                  CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);
                }
              }
            }
            UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
          }
          most_recent++;
        }
        // Need to throw illegal monitor state exception
        CALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD), handle_exception);
        ShouldNotReachHere();
      }

1、循环遍历当前线程的线程栈,找到指向当前锁对象的Lock Record.
2、将Lock Record的obj设置为空,也就是不再让Lock Record指向锁对象。(这个动作偏向锁也会有)
3、判断如果是轻量级锁,然后判断如果Lock Record的Displaced header不为空,则通过CAS将Displaced header中的markoop替换回对象头中。前面讲轻量级锁获取的时候也有提到过,如果是轻量级锁重入,则Lock Record的Displaced header设置为空,这里退出的时候,会判断如果不为空则替换。
4、如果替换成功,则释放锁成功。如果替换失败,则说明当前锁被其他线程抢占过。所以要执行InterpreterRuntime::monitorexit的退出逻辑,monitorexit中,主要做的是轻量级锁的退出和锁膨胀为重量级锁,具体如下:

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
  Handle h_obj(thread, elem->obj());
  assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
         "must be NULL or an object");
  if (elem == NULL || h_obj()->is_unlocked()) {
    THROW(vmSymbols::java_lang_IllegalMonitorStateException());
  }
  ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
  // Free entry. This must be done here, since a pending exception might be installed on
  // exit. If it is not cleared, the exception handling code will try to unlock the monitor again.
  elem->set_obj(NULL);
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END

上面首先执行了ObjectSynchronizer::slow_exit,ObjectSynchronizer::slow_exit中调用的是fast_exit, 然后将Lock Record的obj置为空。fast_exit逻辑如下:

void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
  assert(!object->mark()->has_bias_pattern(), "should not see bias pattern here");
  // if displaced header is null, the previous enter is recursive enter, no-op
  markOop dhw = lock->displaced_header();
  markOop mark;
  //这里判断,Lock Record的displaced_header是否为空,如果是,当前的Lock Record是重入中的一个。
  //如果没有持有轻量级锁,那就没必要执行膨胀了,直接返回。中间assert了一下是否已经是重量级锁,别的什么也没做。
  if (dhw == NULL) {
    // Recursive stack-lock.
    // Diagnostics -- Could be: stack-locked, inflating, inflated.
    mark = object->mark();
    assert(!mark->is_neutral(), "invariant");
    if (mark->has_locker() && mark != markOopDesc::INFLATING()) {
      assert(THREAD->is_lock_owned((address)mark->locker()), "invariant");
    }
    if (mark->has_monitor()) {
      ObjectMonitor * m = mark->monitor();
      assert(((oop)(m->object()))->mark() == mark, "invariant");
      assert(m->is_entered(THREAD), "invariant");
    }
    return;
  }
  //执行到这儿,说明Lock Record的displaced_header不为空,说明可能是轻量级锁,也可能已经膨胀为了重量级锁。
  mark = object->mark();

  // If the object is stack-locked by the current thread, try to
  // swing the displaced header from the box back to the mark.
  //尝试将其强转为markOop,成功,则说明还是轻量级锁,则尝试用CAS将Lock Record的isplaced_header替换回对象头。
  //则尝试释放轻量级锁,即通过CAS将displaced header替换回对象头中,替换成功则说明轻量级锁释放成功。
  if (mark == (markOop) lock) {
    assert(dhw->is_neutral(), "invariant");
    if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
      TEVENT(fast_exit: release stacklock);
      return;
    }
  }
  //开始执行锁的膨胀升级为重量级锁,并且执行exit
  ObjectSynchronizer::inflate(THREAD, object)->exit(true, THREAD);
}

1、这里判断,Lock Record的displaced_header是否为空,如果是,则说明已经没有持有当前轻量级级锁了。如果没有持有轻量级锁,那就没必要执行膨胀了,直接返回。中间assert了一下是否已经是重量级锁,别的什么也没做。
2、如果Lock Record的displaced_header不为空, 判断如果当前对象头还是轻量级锁,并且指向的是当前Lock Record,则尝试释放轻量级锁,即通过CAS将displaced header替换回对象头中,替换成功则说明轻量级锁释放成功。
3、如果前面的判断或者是替换都失败了,就开始执行ObjectSynchronizer::inflate进行锁膨胀为重量级锁。膨胀成功后ObjectSynchronizer::inflate的返回值为ObjectMonitor对象,ObjectMonitor就是用于实现重量级锁的。然后调用ObjectMonitor的exit方法进行锁的释放。

轻量级锁的锁的退出流程:
在这里插入图片描述

3.2.2.3 轻量级锁的膨胀

前面讲解偏向锁,存在获取,退出,撤销重新偏向三种情况,其中在撤销的过程,根据不同的情况,会升级为轻量级锁。而轻量级锁,除了获取,释放,同样存在膨胀为重量级锁的过程,这里我们就分析一下轻量级锁膨胀为重量级锁的过程。

轻量级锁膨胀为重量级锁,要从轻量级锁CAS替换线程栈和对象头和Lock Record的markoop说起,获取和释放轻量级锁的时候都会替换,获取和退出替换失败最终执行膨胀的逻辑其实是一样的,都是ObjectSynchronizer::inflate。这里跟一下进入时的膨胀逻辑。
首先是执行InterpreterRuntime::monitorenter,定义在interpreterRuntime.cpp中:

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
  if (PrintBiasedLockingStatistics) {
    Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
  }
  Handle h_obj(thread, elem->obj());
  assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
         "must be NULL or an object");
  if (UseBiasedLocking) {
    // Retry fast entry if bias is revoked to avoid unnecessary inflation
    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
  } else {
    ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
  }

这段代码,偏向锁中也看到过,不过这里是轻量级锁,所以这里调用的是ObjectSynchronizer::slow_enter, slow_enter的实现如下:

void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
  markOop mark = obj->mark();
  assert(!mark->has_bias_pattern(), "should not see bias pattern here");

//无锁状态
  if (mark->is_neutral()) {
    // Anticipate successful CAS -- the ST of the displaced mark must
    // be visible <= the ST performed by the CAS.
    lock->set_displaced_header(mark);
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
      TEVENT(slow_enter: release stacklock);
      return;
    }
    // Fall through to inflate() ...
    // 轻量级锁重入
  } else if (mark->has_locker() &&
             THREAD->is_lock_owned((address)mark->locker())) {
    assert(lock != mark->locker(), "must not re-lock the same lock");
    assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
    lock->set_displaced_header(NULL);
    return;
  }

  // The object header will never be displaced to this lock,
  // so it does not matter what the value is, except that it
  // must be non-zero to avoid looking like a re-entrant lock,
  // and must not look locked either.
  //开始升级为重量级锁
  lock->set_displaced_header(markOopDesc::unused_mark());
  ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}

这里逻辑很简单,首先又进行了一系列的判断是否是无锁,是否是轻量级锁重入等,都不是,就开始ObjectSynchronizer::inflate进行膨胀。
膨胀之前,将线程栈的Lock Record的displaced_header状态设置为unused_mark,标志位为11。注释的意思为:对象头永远不会被移到这个锁中,所以值是什么并不重要,除非它必须是非零的,以避免看起来像是重入者锁,而且也不能看起来是锁定的。大概可能就是说,已经升级为重量级锁了,这个displaced_header也不会再被用到,所以里面放什么都无所谓了,但是放的值不能让他看起来像是重量级锁。
膨胀成功后,返回值为ObjectMonitor,然后调用ObjectMonitor的enter方法获取锁。
所以膨胀的逻辑就是ObjectSynchronizer::inflate的实现,具体代码如下:

ObjectMonitor * NOINLINE ObjectSynchronizer::inflate(Thread * Self,
                                                     oop object) {
  // Inflate mutates the heap ...
  // Relaxing assertion for bug 6320749.
  assert(Universe::verify_in_progress() ||
         !SafepointSynchronize::is_at_safepoint(), "invariant");

  for (;;) {
    //获取锁对象的markoop
    const markOop mark = object->mark();
    assert(!mark->has_bias_pattern(), "invariant");

    // The mark can be in one of the following states:
    // *  Inflated     - just return
    // *  Stack-locked - coerce it to inflated
    // *  INFLATING    - busy wait for conversion to complete
    // *  Neutral      - aggressively inflate the object.
    // *  BIASED       - Illegal.  We should never see this

    // CASE: inflated
    //判断是否有Objectmonitor,如果有,说明已经膨胀过了,直接返回
    if (mark->has_monitor()) {
      ObjectMonitor * inf = mark->monitor();
      assert(inf->header()->is_neutral(), "invariant");
      assert(inf->object() == object, "invariant");
      assert(ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");
      return inf;
    }

    // CASE: inflation in progress - inflating over a stack-lock.
    // Some other thread is converting from stack-locked to inflated.
    // Only that thread can complete inflation -- other threads must wait.
    // The INFLATING value is transient.
    // Currently, we spin/yield/park and poll the markword, waiting for inflation to finish.
    // We could always eliminate polling by parking the thread on some auxiliary list.
    //如果有其他线程正在执行膨胀,则当前线程不再去执行膨胀逻辑
    if (mark == markOopDesc::INFLATING()) {
      TEVENT(Inflate: spin while INFLATING);
      ReadStableMark(object);
      continue;
    }

    // CASE: stack-locked
    // Could be stack-locked either by this thread or by some other thread.
    //
    // Note that we allocate the objectmonitor speculatively, _before_ attempting
    // to install INFLATING into the mark word.  We originally installed INFLATING,
    // allocated the objectmonitor, and then finally STed the address of the
    // objectmonitor into the mark.  This was correct, but artificially lengthened
    // the interval in which INFLATED appeared in the mark, thus increasing
    // the odds of inflation contention.
    //
    // We now use per-thread private objectmonitor free lists.
    // These list are reprovisioned from the global free list outside the
    // critical INFLATING...ST interval.  A thread can transfer
    // multiple objectmonitors en-mass from the global free list to its local free list.
    // This reduces coherency traffic and lock contention on the global free list.
    // Using such local free lists, it doesn't matter if the omAlloc() call appears
    // before or after the CAS(INFLATING) operation.
    // See the comments in omAlloc().

    //判断目前对象头的状态是否是轻量级锁的状态,如果是则执行
    if (mark->has_locker()) {
    //创建一个ObjectMonitor
      ObjectMonitor * m = omAlloc(Self);
      // Optimistically prepare the objectmonitor - anticipate successful CAS
      // We do this before the CAS in order to minimize the length of time
      // in which INFLATING appears in the mark.
      m->Recycle();
      m->_Responsible  = NULL;
      m->_recursions   = 0;
      m->_SpinDuration = ObjectMonitor::Knob_SpinLimit;   // Consider: maintain by type/class
      //设置对象的状态为膨胀中,设置失败则重新执行膨胀逻辑
      markOop cmp = (markOop) Atomic::cmpxchg_ptr(markOopDesc::INFLATING(), object->mark_addr(), mark);
      if (cmp != mark) {
        omRelease(Self, m, true);
        continue;       // Interference -- just retry
      }

      // We've successfully installed INFLATING (0) into the mark-word.
      // This is the only case where 0 will appear in a mark-word.
      // Only the singular thread that successfully swings the mark-word
      // to 0 can perform (or more precisely, complete) inflation.
      //
      // Why do we CAS a 0 into the mark-word instead of just CASing the
      // mark-word from the stack-locked value directly to the new inflated state?
      // Consider what happens when a thread unlocks a stack-locked object.
      // It attempts to use CAS to swing the displaced header value from the
      // on-stack basiclock back into the object header.  Recall also that the
      // header value (hashcode, etc) can reside in (a) the object header, or
      // (b) a displaced header associated with the stack-lock, or (c) a displaced
      // header in an objectMonitor.  The inflate() routine must copy the header
      // value from the basiclock on the owner's stack to the objectMonitor, all
      // the while preserving the hashCode stability invariants.  If the owner
      // decides to release the lock while the value is 0, the unlock will fail
      // and control will eventually pass from slow_exit() to inflate.  The owner
      // will then spin, waiting for the 0 value to disappear.   Put another way,
      // the 0 causes the owner to stall if the owner happens to try to
      // drop the lock (restoring the header from the basiclock to the object)
      // while inflation is in-progress.  This protocol avoids races that might
      // would otherwise permit hashCode values to change or "flicker" for an object.
      // Critically, while object->mark is 0 mark->displaced_mark_helper() is stable.
      // 0 serves as a "BUSY" inflate-in-progress indicator.


      // fetch the displaced mark from the owner's stack.
      // The owner can't die or unwind past the lock while our INFLATING
      // object is in the mark.  Furthermore the owner can't complete
      // an unlock on the object, either.
      markOop dmw = mark->displaced_mark_helper();
      assert(dmw->is_neutral(), "invariant");

      // Setup monitor fields to proper values -- prepare the monitor
      m->set_header(dmw);

      // Optimization: if the mark->locker stack address is associated
      // with this thread we could simply set m->_owner = Self.
      // Note that a thread can inflate an object
      // that it has stack-locked -- as might happen in wait() -- directly
      // with CAS.  That is, we can avoid the xchg-NULL .... ST idiom.
      m->set_owner(mark->locker());
      m->set_object(object);
      // TODO-FIXME: assert BasicLock->dhw != 0.

      // Must preserve store ordering. The monitor state must
      // be stable at the time of publishing the monitor address.
      guarantee(object->mark() == markOopDesc::INFLATING(), "invariant");
      //设置对象头指向当前的objectmonitor
      object->release_set_mark(markOopDesc::encode(m));

      // Hopefully the performance counters are allocated on distinct cache lines
      // to avoid false sharing on MP systems ...
      OM_PERFDATA_OP(Inflations, inc());
      TEVENT(Inflate: overwrite stacklock);
      if (log_is_enabled(Debug, monitorinflation)) {
        if (object->is_instance()) {
          ResourceMark rm;
          log_debug(monitorinflation)("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
                                      p2i(object), p2i(object->mark()),
                                      object->klass()->external_name());
        }
      }
      return m;
    }

    // CASE: neutral
    // TODO-FIXME: for entry we currently inflate and then try to CAS _owner.
    // If we know we're inflating for entry it's better to inflate by swinging a
    // pre-locked objectMonitor pointer into the object header.   A successful
    // CAS inflates the object *and* confers ownership to the inflating thread.
    // In the current implementation we use a 2-step mechanism where we CAS()
    // to inflate and then CAS() again to try to swing _owner from NULL to Self.
    // An inflateTry() method that we could call from fast_enter() and slow_enter()
    // would be useful.

    assert(mark->is_neutral(), "invariant");
    //创建ObjectMonitor
    ObjectMonitor * m = omAlloc(Self);
    // prepare m for installation - set monitor to initial state
    m->Recycle();
    m->set_header(mark);
    m->set_owner(NULL);
    m->set_object(object);
    m->_recursions   = 0;
    m->_Responsible  = NULL;
    m->_SpinDuration = ObjectMonitor::Knob_SpinLimit;       // consider: keep metastats by type/class

    //把对象头替换为执行objectMonitor的指针
    if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
      m->set_object(NULL);
      m->set_owner(NULL);
      m->Recycle();
      omRelease(Self, m, true);
      m = NULL;
      continue;
      // interference - the markword changed - just retry.
      // The state-transitions are one-way, so there's no chance of
      // live-lock -- "Inflated" is an absorbing state.
    }

    // Hopefully the performance counters are allocated on distinct
    // cache lines to avoid false sharing on MP systems ...
    OM_PERFDATA_OP(Inflations, inc());
    TEVENT(Inflate: overwrite neutral);
    if (log_is_enabled(Debug, monitorinflation)) {
      if (object->is_instance()) {
        ResourceMark rm;
        log_debug(monitorinflation)("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
                                    p2i(object), p2i(object->mark()),
                                    object->klass()->external_name());
      }
    }
    return m;
  }
}

看似比较长,逻辑其实很简单。
1、判断是否有Objectmonitor,如果已经有了,说明已经是重量级锁了,直接返回Objectmonitor。
2、判断是否有其他线程正在执行锁的膨胀,如果是,则continue重新执行膨胀判断逻辑,也就是继续第一步的判断。
3、如果没有Objectmonitor,则进行膨胀。膨胀过程也就是创建一个ObjectMonitor,并且进行初始化,然后将锁对象的的markoop指向Objectmonitor,然后返回。

重量级锁到这里,就大致讲完了,重量级锁需要注意的地方是,重量级锁重入,也会通过CAS进行替换,替换失败,则判断是否是重入。轻量级锁释放的时候,也需要通过CAS替换。

3.3.3 重量级锁

前面也看到了,重量级锁是通过ObjectMonitor实现的,升级为重量级锁后,锁对象会指向objectmonitor。objectmonitor其实就是一个对象,其中定义了很多用于保存锁相关信息的字段。具体如下:

private:
  friend class ObjectSynchronizer;
  friend class ObjectWaiter;
  friend class VMStructs;

  volatile markOop   _header;       // displaced object header word - mark  ,保存锁对象的对象头markoop
  void*     volatile _object;       // backward object pointer - strong root,指向锁对象的指针
 public:
  ObjectMonitor *    FreeNext;      // Free list linkage
 private:
  DEFINE_PAD_MINUS_SIZE(0, DEFAULT_CACHE_LINE_SIZE,
                        sizeof(volatile markOop) + sizeof(void * volatile) +
                        sizeof(ObjectMonitor *));
 protected:                         // protected for JvmtiRawMonitor
  void *  volatile _owner;          // pointer to owning thread OR BasicLock,指向拥有当前锁的线程
  volatile jlong _previous_owner_tid;  // thread id of the previous owner of the monitor
  volatile intptr_t  _recursions;   // recursion count, 0 for first entry,线程锁重入次数
  ObjectWaiter * volatile _EntryList; // Threads blocked on entry or reentry.,被阻塞的线程重新进入时,会将其放在当前队列中。其实这个队列是被wait()方法阻塞的线程,当调用notify/notifyAll时,会将线程放在这个队列中。
                                      // The list is actually composed of WaitNodes,
                                      // acting as proxies for Threads.
 private:
  ObjectWaiter * volatile _cxq;     // LL of recently-arrived threads blocked on entry. 当对象锁已经被一个线程持有,其他所有的线程在尝试获取锁的时候,如果没有获取到,将其挂起后都会被放在这个队列上。
  Thread * volatile _succ;          // Heir presumptive thread - used for futile wakeup throttling
  Thread * volatile _Responsible;

  volatile int _Spinner;            // for exit->spinner handoff optimization,Spinner相关的都是和自旋锁优化相关
  volatile int _SpinFreq;           // Spin 1-out-of-N attempts: success rate
  volatile int _SpinClock;
  volatile intptr_t _SpinState;     // MCS/CLH list of spinners
  volatile int _SpinDuration;

  volatile jint  _count;            // reference count to prevent reclamation/deflation
                                    // at stop-the-world time.  See deflate_idle_monitors().
                                    // _count is approximately |_WaitSet| + |_EntryList|
 protected:
  ObjectWaiter * volatile _WaitSet; // LL of threads wait()ing on the monitor,调用wait()方法阻塞的线程,都会放在当前队列中
  volatile jint  _waiters;          // number of waiting threads
 private:
  volatile int _WaitSetLock;        // protects Wait Queue - simple spinlock

我们这里着重注意一下以下几个字段:

volatile markOop   _header;       // displaced object header word - mark  ,保存锁对象的对象头markoop
void*     volatile _object;       // backward object pointer - strong root,指向锁对象的指针
void *  volatile _owner;          // pointer to owning thread OR BasicLock,指向拥有当前锁的线程
volatile intptr_t  _recursions;   // recursion count, 0 for first entry,线程锁重入次数
ObjectWaiter * volatile _EntryList; // Threads blocked on entry or reentry.,被阻塞的线程重新进入时,会将其放在当前队列中。其实这个队列是被wait()方法阻塞的线程,当调用notify/notifyAll时,会将准备唤醒的线程放在这个队列中。
ObjectWaiter * volatile _cxq;     // LL of recently-arrived threads blocked on entry. 当对象锁已经被一个线程持有,其他所有的线程在尝试获取锁的时候,如果没有获取到,将其挂起后都会被放在这个队列上。
ObjectWaiter * volatile _WaitSet; // LL of threads wait()ing on the monitor,调用wait()方法阻塞的线程,都会放在当前队列中

看了objectmonitor定义即各个字段的作用后,接下来分析重量级锁的获取以及释放的流程。

3.3.3.1 重量级锁的获取

前面看到,如果轻量级锁成功膨胀为了重量级锁,则执行膨胀objectmonitor的enter方法获取重量级锁,所以重量级锁的获取,是ObjectMonitor::enter方法,定义在objectMonitor.cpp中。

void NOINLINE ObjectMonitor::enter(TRAPS) {
  // The following code is ordered to check the most common cases first
  // and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
  Thread * const Self = THREAD;
  //通过CAS将objectmonitor的owner替换成当前线程,如果owner则替换成功。
  void * cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL);
  if (cur == NULL) {
  //说明替换成功了,则已经获取了锁,直接返回即可
    // Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
    assert(_recursions == 0, "invariant");
    assert(_owner == Self, "invariant");
    return;
  }

//执行到这里,说明替换失败了,则判断owner是不是自己,如果是自己,则说明是锁重入,直接将重入次数++,返回。
  if (cur == Self) {
    // TODO-FIXME: check for integer overflow!  BUGID 6557169.
    _recursions++;
    return;
  }
  //is_lock_owned判断的是,owner指向的地址是否是当前线程栈上的某一地址,如果是,则直接将owner执行当前线程,然后返回
  if (Self->is_lock_owned ((address)cur)) {
    assert(_recursions == 0, "internal state error");
    _recursions = 1;
    // Commute owner from a thread-specific on-stack BasicLockObject address to
    // a full-fledged "Thread *".
    _owner = Self;
    return;
  }

  // We've encountered genuine contention.
  assert(Self->_Stalled == 0, "invariant");
  Self->_Stalled = intptr_t(this);

  // Try one round of spinning *before* enqueueing Self
  // and before going through the awkward and expensive state
  // transitions.  The following spin is strictly optional ...
  // Note that if we acquire the monitor from an initial spin
  // we forgo posting JVMTI events and firing DTRACE probes.
  //执行到这里,说当前锁已经被其他线程占有了,
  //但是,在执行昂贵的线程挂起操作之前,先初始化一个自旋监视器,尝试自旋一下。
  //在自旋的过程中会不断的尝试去获取锁。
  if (Knob_SpinEarly && TrySpin (Self) > 0) {
    assert(_owner == Self, "invariant");
    assert(_recursions == 0, "invariant");
    assert(((oop)(object()))->mark() == markOopDesc::encode(this), "invariant");
    Self->_Stalled = 0;
    return;
  }

  assert(_owner != Self, "invariant");
  assert(_succ != Self, "invariant");
  assert(Self->is_Java_thread(), "invariant");
  JavaThread * jt = (JavaThread *) Self;
  assert(!SafepointSynchronize::is_at_safepoint(), "invariant");
  assert(jt->thread_state() != _thread_blocked, "invariant");
  assert(this->object() != NULL, "invariant");
  assert(_count >= 0, "invariant");

  // Prevent deflation at STW-time.  See deflate_idle_monitors() and is_busy().
  // Ensure the object-monitor relationship remains stable while there's contention.
  Atomic::inc(&_count);

  EventJavaMonitorEnter event;

  { // Change java thread status to indicate blocked on monitor enter.
  //更改java线程的状态为进入监视器时被阻塞,也就是java线程block状态
    JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);

    DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);
    if (JvmtiExport::should_post_monitor_contended_enter()) {
      JvmtiExport::post_monitor_contended_enter(jt, this);

      // The current thread does not yet own the monitor and does not
      // yet appear on any queues that would get it made the successor.
      // This means that the JVMTI_EVENT_MONITOR_CONTENDED_ENTER event
      // handler cannot accidentally consume an unpark() meant for the
      // ParkEvent associated with this ObjectMonitor.
    }

    OSThreadContendState osts(Self->osthread());
    ThreadBlockInVM tbivm(jt);

    //设置线程当前阻塞在的monitor为在当前的monitor。
    Self->set_current_pending_monitor(this);

    // TODO-FIXME: change the following for(;;) loop to straight-line code.
    for (;;) {
      jt->set_suspend_equivalent();
      // cleared by handle_special_suspend_equivalent_condition()
      // or java_suspend_self()
      //EnterI是线程挂起的真正逻辑
      EnterI(THREAD);

      if (!ExitSuspendEquivalent(jt)) break;

      // We have acquired the contended monitor, but while we were
      // waiting another thread suspended us. We don't want to enter
      // the monitor while suspended because that would surprise the
      // thread that suspended us.
      //
      _recursions = 0;
      _succ = NULL;
      exit(false, Self);

      jt->java_suspend_self();
    }
    Self->set_current_pending_monitor(NULL);

    // We cleared the pending monitor info since we've just gotten past
    // the enter-check-for-suspend dance and we now own the monitor free
    // and clear, i.e., it is no longer pending. The ThreadBlockInVM
    // destructor can go to a safepoint at the end of this block. If we
    // do a thread dump during that safepoint, then this thread will show
    // as having "-locked" the monitor, but the OS and java.lang.Thread
    // states will still report that the thread is blocked trying to
    // acquire it.
  }
	......
	......
}

流程看似较长,核心逻辑不难理解:

  1. 通过CAS尝试将ObjectMonitor的owner替换指向当前线程。如果有owner == null情况下才会成功。

  2. 替换成功,则说明换成功,就可以返回了,如果替换失败,则判断owner是不是等于自己,如果是,那就是锁重入了,只需要将重入次数++,然后返回。

  3. 如果1,2步骤都失败了,说明当前锁已经被其他线程获取了,当前线程会进行自旋,不断的自旋尝试获取锁,这里简单的看一下自旋和自适应自旋的概念:
    前面提到过,线程的阻塞需要挂起线程和恢复线程,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性能带来了很大的压力。同时,虚拟机的开发团队也注意到在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。这种时候,我们就可以让后面请求锁的那个线程“稍等一下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。
    自旋锁在JDK 1.4.2中就已经引入,只不过默认是关闭的,可以使用-XX:+UseSpinning参数来开启,在JDK 1.6中就已经改为默认开启了。自旋等待不能代替阻塞,因为自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时间很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性能上的浪费。因此,自旋等待的时间必须要有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程了。自旋次数的默认值是10次,用户可以使用参数-XX:PreBlockSpin来更改。
    在JDK 1.6中引入了自适应的自旋锁。自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更长的时间,比如100个循环。另外,如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。有了自适应自旋,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,虚拟机就会变得越来越“聪明”了。

  4. 如果自旋获取锁成功了,则返回,如果没有,就要开始进行线程的起操作了。

  5. 挂起前,设置当前线程的状态为阻塞状态,即Block,然后设置当前线程挂起在了当前对象上。然后执行EnterI(THREAD);开始真正的挂起操作。EnterI(THREAD);定义如下:

void NOINLINE ObjectMonitor::EnterI(TRAPS) {
  Thread * const Self = THREAD;
  assert(Self->is_Java_thread(), "invariant");
  assert(((JavaThread *) Self)->thread_state() == _thread_blocked, "invariant");

  // Try the lock - TATAS
  if (TryLock (Self) > 0) {
    assert(_succ != Self, "invariant");
    assert(_owner == Self, "invariant");
    assert(_Responsible != Self, "invariant");
    return;
  }

  DeferredInitialize();

  // We try one round of spinning *before* enqueueing Self.
  //
  // If the _owner is ready but OFFPROC we could use a YieldTo()
  // operation to donate the remainder of this thread's quantum
  // to the owner.  This has subtle but beneficial affinity
  // effects.

  if (TrySpin (Self) > 0) {
    assert(_owner == Self, "invariant");
    assert(_succ != Self, "invariant");
    assert(_Responsible != Self, "invariant");
    return;
  }

  // The Spin failed -- Enqueue and park the thread ...
  assert(_succ != Self, "invariant");
  assert(_owner != Self, "invariant");
  assert(_Responsible != Self, "invariant");

  // Enqueue "Self" on ObjectMonitor's _cxq.
  //
  // Node acts as a proxy for Self.
  // As an aside, if were to ever rewrite the synchronization code mostly
  // in Java, WaitNodes, ObjectMonitors, and Events would become 1st-class
  // Java objects.  This would avoid awkward lifecycle and liveness issues,
  // as well as eliminate a subset of ABA issues.
  // TODO: eliminate ObjectWaiter and enqueue either Threads or Events.

  ObjectWaiter node(Self);
  Self->_ParkEvent->reset();
  node._prev   = (ObjectWaiter *) 0xBAD;
  node.TState  = ObjectWaiter::TS_CXQ;

  // Push "Self" onto the front of the _cxq.
  // Once on cxq/EntryList, Self stays on-queue until it acquires the lock.
  // Note that spinning tends to reduce the rate at which threads
  // enqueue and dequeue on EntryList|cxq.
  ObjectWaiter * nxt;
  for (;;) {
    node._next = nxt = _cxq;
    //尝试将当前线程放在cxq队列的头部,如果放成功了则跳出循环
    if (Atomic::cmpxchg_ptr(&node, &_cxq, nxt) == nxt) break;

    // Interference - the CAS failed because _cxq changed.  Just retry.
    // As an optional optimization we retry the lock.
    //放失败了,则说明cxq对象被改变了,则尝试获取锁。
    //获取成功则直接返回,获取失败,在继续尝试将线程放在Cxq队列的头部
    if (TryLock (Self) > 0) {
      assert(_succ != Self, "invariant");
      assert(_owner == Self, "invariant");
      assert(_Responsible != Self, "invariant");
      return;
    }
  }

  // Check for cxq|EntryList edge transition to non-null.  This indicates
  // the onset of contention.  While contention persists exiting threads
  // will use a ST:MEMBAR:LD 1-1 exit protocol.  When contention abates exit
  // operations revert to the faster 1-0 mode.  This enter operation may interleave
  // (race) a concurrent 1-0 exit operation, resulting in stranding, so we
  // arrange for one of the contending thread to use a timed park() operations
  // to detect and recover from the race.  (Stranding is form of progress failure
  // where the monitor is unlocked but all the contending threads remain parked).
  // That is, at least one of the contended threads will periodically poll _owner.
  // One of the contending threads will become the designated "Responsible" thread.
  // The Responsible thread uses a timed park instead of a normal indefinite park
  // operation -- it periodically wakes and checks for and recovers from potential
  // strandings admitted by 1-0 exit operations.   We need at most one Responsible
  // thread per-monitor at any given moment.  Only threads on cxq|EntryList may
  // be responsible for a monitor.
  //
  // Currently, one of the contended threads takes on the added role of "Responsible".
  // A viable alternative would be to use a dedicated "stranding checker" thread
  // that periodically iterated over all the threads (or active monitors) and unparked
  // successors where there was risk of stranding.  This would help eliminate the
  // timer scalability issues we see on some platforms as we'd only have one thread
  // -- the checker -- parked on a timer.

   //这里判断的是,如果nex==null(说明cxq队列为空),并且entryList为空。
   //那说明当前线程是第一个阻塞或者等待当前锁的线程
  if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
    // Try to assume the role of responsible thread for the monitor.
    // CONSIDER:  ST vs CAS vs { if (Responsible==null) Responsible=Self }
    //通过cas将_Responsible指针指向Self
    Atomic::cmpxchg_ptr(Self, &_Responsible, NULL);
  }

  // The lock might have been released while this thread was occupied queueing
  // itself onto _cxq.  To close the race and avoid "stranding" and
  // progress-liveness failure we must resample-retry _owner before parking.
  // Note the Dekker/Lamport duality: ST cxq; MEMBAR; LD Owner.
  // In this case the ST-MEMBAR is accomplished with CAS().
  //
  // TODO: Defer all thread state transitions until park-time.
  // Since state transitions are heavy and inefficient we'd like
  // to defer the state transitions until absolutely necessary,
  // and in doing so avoid some transitions ...

  TEVENT(Inflated enter - Contention);
  int nWakeups = 0;
  int recheckInterval = 1;
  //执行到这儿,说明线程已经被成功放在了cxq队列的头部,
  //然后下面进入一个循环,只有成功获取到了锁,才能够跳出循环。
  for (;;) {
    //再次尝试获取锁
    if (TryLock(Self) > 0) break;
    assert(_owner != Self, "invariant");
    //如果_Responsible指针为NULL,则再次尝试让_Responsible指向当前线程
    if ((SyncFlags & 2) && _Responsible == NULL) {
      Atomic::cmpxchg_ptr(Self, &_Responsible, NULL);
    }

    // park self
    //如果_Responsible成功指向了当前线程,说明当前线程是第一个被阻塞或者等待获取锁的线程。
    //则会执行一个简单的退避算法。执行有等待时间的park操作,第一次是1ms
    //每次到时间后自己去尝试获取锁,获取失败后继续睡眠,每次睡眠的时间是上一次的8倍。
    if (_Responsible == Self || (SyncFlags & 1)) {
      TEVENT(Inflated enter - park TIMED);
      Self->_ParkEvent->park((jlong) recheckInterval);
      // Increase the recheckInterval, but clamp the value.
      recheckInterval *= 8;
      if (recheckInterval > MAX_RECHECK_INTERVAL) {
        recheckInterval = MAX_RECHECK_INTERVAL;
      }
    } else {
    //如果当前线程不是第一个阻塞或者等待锁的线程,则直接park。
      TEVENT(Inflated enter - park UNTIMED);
      Self->_ParkEvent->park();
    }

    if (TryLock(Self) > 0) break;

    // The lock is still contested.
    // Keep a tally of the # of futile wakeups.
    // Note that the counter is not protected by a lock or updated by atomics.
    // That is by design - we trade "lossy" counters which are exposed to
    // races during updates for a lower probe effect.
    TEVENT(Inflated enter - Futile wakeup);
    // This PerfData object can be used in parallel with a safepoint.
    // See the work around in PerfDataManager::destroy().
    OM_PERFDATA_OP(FutileWakeups, inc());
    ++nWakeups;

    // Assuming this is not a spurious wakeup we'll normally find _succ == Self.
    // We can defer clearing _succ until after the spin completes
    // TrySpin() must tolerate being called with _succ == Self.
    // Try yet another round of adaptive spinning.
    if ((Knob_SpinAfterFutile & 1) && TrySpin(Self) > 0) break;

    // We can find that we were unpark()ed and redesignated _succ while
    // we were spinning.  That's harmless.  If we iterate and call park(),
    // park() will consume the event and return immediately and we'll
    // just spin again.  This pattern can repeat, leaving _succ to simply
    // spin on a CPU.  Enable Knob_ResetEvent to clear pending unparks().
    // Alternately, we can sample fired() here, and if set, forgo spinning
    // in the next iteration.

    if ((Knob_ResetEvent & 1) && Self->_ParkEvent->fired()) {
      Self->_ParkEvent->reset();
      OrderAccess::fence();
    }
    if (_succ == Self) _succ = NULL;

    // Invariant: after clearing _succ a thread *must* retry _owner before parking.
    OrderAccess::fence();
  }
    //执行到这里,说明当前线程成功获取了锁,所以下面要做的事情,就是要将当前线程从当前锁的阻塞队列上去掉,
  // Egress :
  // Self has acquired the lock -- Unlink Self from the cxq or EntryList.
  // Normally we'll find Self on the EntryList .
  // From the perspective of the lock owner (this thread), the
  // EntryList is stable and cxq is prepend-only.
  // The head of cxq is volatile but the interior is stable.
  // In addition, Self.TState is stable.

  assert(_owner == Self, "invariant");
  assert(object() != NULL, "invariant");
  // I'd like to write:
  //   guarantee (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
  // but as we're at a safepoint that's not safe.
  //从队列中去掉当前线程的操作
  UnlinkAfterAcquire(Self, &node);
  if (_succ == Self) _succ = NULL;

  assert(_succ != Self, "invariant");
  //如果_Responsible是当前线程,则将_Responsible设置为NULL
  if (_Responsible == Self) {
    _Responsible = NULL;
    OrderAccess::fence(); // Dekker pivot-point
  }
  return;
}
  1. 首先还是尝试获取锁,获取失败后再尝试自旋一下,自旋依旧失败,之后是真正的挂起逻辑。

  2. 执行真正挂起逻辑之前,首先将自己包装成一个ObjectWaiter对象,并通过CAS放在Cxq队列的头部。

  3. 执行真正挂起逻辑的时候,有一个指针_Responsible,当cxq队列和_EntryList为空,并且_Responsible为空的时候,说明当前线程是第一个等待锁的线程,就通过CAS将_Responsible指向当前线程。
    然后,进入到一个无限循环中,只有成功获取了锁,才能退出循环。

  4. 如果_Responsible指向的是当前程,就通过一个简单的退避算法进行有条件的挂起,第一次1ms,第二次8ms,每次下一次都是上一次睡眠时间的8倍,但时间自动唤醒自己并尝试获取锁。

  5. 如果_Responsible指向的不是当前线程,则当前线程会执行park将自己挂起,只有当前得到锁的线程释放锁的时候,才有机会被唤醒并且竞争锁。

  6. 最后,当获取到锁之后,就会跳出循环,填出循环后需要做一些后续处理,需要把自己从等待队列中移除,如果_Responsible执行自己,需要把_Responsible置为空。

重量级锁的获取流程如下:
在这里插入图片描述

3.3.3.1 重量级锁的释放

重量级锁的释放,执行的是ObjectMonitor的exit方法,方法如下:

void NOINLINE ObjectMonitor::exit(bool not_suspended, TRAPS) {
  Thread * const Self = THREAD;
  //objectMonitor的owner没有指向当前线程
  if (THREAD != _owner) {
  //判断objectMonitor的owner是否指向当前线程的线程栈(这个在执行enter的时候,也有判断过这种情况)
    if (THREAD->is_lock_owned((address) _owner)) {
      // Transmute _owner from a BasicLock pointer to a Thread address.
      // We don't need to hold _mutex for this transition.
      // Non-null to Non-null is safe as long as all readers can
      // tolerate either flavor.
      assert(_recursions == 0, "invariant");
      _owner = THREAD;
      _recursions = 0;
    } else {
      // Apparent unbalanced locking ...
      // Naively we'd like to throw IllegalMonitorStateException.
      // As a practical matter we can neither allocate nor throw an
      // exception as ::exit() can be called from leaf routines.
      // see x86_32.ad Fast_Unlock() and the I1 and I2 properties.
      // Upon deeper reflection, however, in a properly run JVM the only
      // way we should encounter this situation is in the presence of
      // unbalanced JNI locking. TODO: CheckJNICalls.
      // See also: CR4414101
      TEVENT(Exit - Throw IMSX);
      assert(false, "Non-balanced monitor enter/exit! Likely JNI locking");
      return;
    }
  }

//重入次数--
  if (_recursions != 0) {
    _recursions--;        // this is simple recursive enter
    TEVENT(Inflated exit - recursive);
    return;
  }

  // Invariant: after setting Responsible=null an thread must execute
  // a MEMBAR or other serializing instruction before fetching EntryList|cxq.
  if ((SyncFlags & 4) == 0) {
    _Responsible = NULL;
  }

#if INCLUDE_TRACE
  // get the owner's thread id for the MonitorEnter event
  // if it is enabled and the thread isn't suspended
  if (not_suspended && Tracing::is_event_enabled(TraceJavaMonitorEnterEvent)) {
    _previous_owner_tid = SharedRuntime::get_java_tid(Self);
  }
#endif

  for (;;) {
    assert(THREAD == _owner, "invariant");

    //这里根据不同的退出策略进行退出
    if (Knob_ExitPolicy == 0) {
      // release semantics: prior loads and stores from within the critical section
      // must not float (reorder) past the following store that drops the lock.
      // On SPARC that requires MEMBAR #loadstore|#storestore.
      // But of course in TSO #loadstore|#storestore is not required.
      // I'd like to write one of the following:
      // A.  OrderAccess::release() ; _owner = NULL
      // B.  OrderAccess::loadstore(); OrderAccess::storestore(); _owner = NULL;
      // Unfortunately OrderAccess::release() and OrderAccess::loadstore() both
      // store into a _dummy variable.  That store is not needed, but can result
      // in massive wasteful coherency traffic on classic SMP systems.
      // Instead, I use release_store(), which is implemented as just a simple
      // ST on x64, x86 and SPARC.
      //释放锁,这里提前释放了锁之后,没有立即从cxq或者是entryList中唤醒一个线程,外面正在抢占锁的线程可以提前获取到锁。
      OrderAccess::release_store_ptr(&_owner, NULL);   // drop the lock
      //增加内存屏障
      OrderAccess::storeload();                        // See if we need to wake a successor
        //后面是从cxq或者_EntryList中唤醒线程的逻辑,这里判断如果_cxq和_EntryList都是空的情况下,直接返回。
      if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
        TEVENT(Inflated exit - simple egress);
        return;
      }
      TEVENT(Inflated exit - complex egress);
      // Other threads are blocked trying to acquire the lock.

      // Normally the exiting thread is responsible for ensuring succession,
      // but if other successors are ready or other entering threads are spinning
      // then this thread can simply store NULL into _owner and exit without
      // waking a successor.  The existence of spinners or ready successors
      // guarantees proper succession (liveness).  Responsibility passes to the
      // ready or running successors.  The exiting thread delegates the duty.
      // More precisely, if a successor already exists this thread is absolved
      // of the responsibility of waking (unparking) one.
      //
      // The _succ variable is critical to reducing futile wakeup frequency.
      // _succ identifies the "heir presumptive" thread that has been made
      // ready (unparked) but that has not yet run.  We need only one such
      // successor thread to guarantee progress.
      // See http://www.usenix.org/events/jvm01/full_papers/dice/dice.pdf
      // section 3.3 "Futile Wakeup Throttling" for details.
      //
      // Note that spinners in Enter() also set _succ non-null.
      // In the current implementation spinners opportunistically set
      // _succ so that exiting threads might avoid waking a successor.
      // Another less appealing alternative would be for the exiting thread
      // to drop the lock and then spin briefly to see if a spinner managed
      // to acquire the lock.  If so, the exiting thread could exit
      // immediately without waking a successor, otherwise the exiting
      // thread would need to dequeue and wake a successor.
      // (Note that we'd need to make the post-drop spin short, but no
      // shorter than the worst-case round-trip cache-line migration time.
      // The dropped lock needs to become visible to the spinner, and then
      // the acquisition of the lock by the spinner must become visible to
      // the exiting thread).

      // It appears that an heir-presumptive (successor) must be made ready.
      // Only the current lock owner can manipulate the EntryList or
      // drain _cxq, so we need to reacquire the lock.  If we fail
      // to reacquire the lock the responsibility for ensuring succession
      // falls to the new owner.
      //
      //使用CAS替换owmer为当前线程,因为后面要从cxq或者entryList中唤醒一个线程,但是只有持有当前锁的线程才能才做cxq和entryList。
      //正如前面释放锁时候解释的那样,上面释放锁,是为了让外面正在抢占锁的线程提前获取锁,这里尝试替换,如果失败,说明锁已经被抢占。成功则执行唤醒逻辑。
      if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
        return;
      }
      TEVENT(Exit - Reacquired);
    } else {
      if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
        OrderAccess::release_store_ptr(&_owner, NULL);   // drop the lock
        OrderAccess::storeload();
        // Ratify the previously observed values.
        if (_cxq == NULL || _succ != NULL) {
          TEVENT(Inflated exit - simple egress);
          return;
        }

        // inopportune interleaving -- the exiting thread (this thread)
        // in the fast-exit path raced an entering thread in the slow-enter
        // path.
        // We have two choices:
        // A.  Try to reacquire the lock.
        //     If the CAS() fails return immediately, otherwise
        //     we either restart/rerun the exit operation, or simply
        //     fall-through into the code below which wakes a successor.
        // B.  If the elements forming the EntryList|cxq are TSM
        //     we could simply unpark() the lead thread and return
        //     without having set _succ.
        if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
          TEVENT(Inflated exit - reacquired succeeded);
          return;
        }
        TEVENT(Inflated exit - reacquired failed);
      } else {
        TEVENT(Inflated exit - complex egress);
      }
    }

    guarantee(_owner == THREAD, "invariant");
    //这里就开始真正开始进行唤醒线程的逻辑了,这里的W将是后面唤醒的线程。
    ObjectWaiter * w = NULL;
    int QMode = Knob_QMode;
    //这里根据不同的模式进行不同的操作。
    if (QMode == 2 && _cxq != NULL) {
      // QMode == 2 : cxq has precedence over EntryList.
      // Try to directly wake a successor from the cxq.
      // If successful, the successor will need to unlink itself from cxq.
      //这种模式下,如果CXQ不等于空,直接从CXQ的头部取出ObjectWaiter进行唤醒。
      w = _cxq;
      assert(w != NULL, "invariant");
      assert(w->TState == ObjectWaiter::TS_CXQ, "Invariant");
      ExitEpilog(Self, w);
      return;
    }

    if (QMode == 3 && _cxq != NULL) {
      // Aggressively drain cxq into EntryList at the first opportunity.
      // This policy ensure that recently-run threads live at the head of EntryList.
      // Drain _cxq into EntryList - bulk transfer.
      // First, detach _cxq.
      // The following loop is tantamount to: w = swap(&cxq, NULL)
      w = _cxq;
      //把&_cxq赋值为NULL。
      for (;;) {
        assert(w != NULL, "Invariant");
        ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr(NULL, &_cxq, w);
        //如果替换成功,直接break跳出。失败则将队首的值u赋值给w,继续尝试替换。
        if (u == w) break;
        w = u;
      }
      assert(w != NULL, "invariant");
      //这一段队列操作,其实就是将cxq队列转换成一个双向队列
      ObjectWaiter * q = NULL;
      ObjectWaiter * p;
      for (p = w; p != NULL; p = p->_next) {
        guarantee(p->TState == ObjectWaiter::TS_CXQ, "Invariant");
        p->TState = ObjectWaiter::TS_ENTER;
        p->_prev = q;
        q = p;
      }

      // Append the RATs to the EntryList
      // TODO: organize EntryList as a CDLL so we can locate the tail in constant-time.
      ObjectWaiter * Tail;
      //这个操作结束,使得tail指向了_EntryList最后一个节点。
      for (Tail = _EntryList; Tail != NULL && Tail->_next != NULL;
           Tail = Tail->_next)
        /* empty */;
       //下面的操作,是将CXQ链接在_EntryList的末尾。
      if (Tail == NULL) {
        _EntryList = w;
      } else {
        Tail->_next = w;
        w->_prev = Tail;
      }

      // Fall thru into code that tries to wake a successor from EntryList
    }

    if (QMode == 4 && _cxq != NULL) {
      // Aggressively drain cxq into EntryList at the first opportunity.
      // This policy ensure that recently-run threads live at the head of EntryList.

      // Drain _cxq into EntryList - bulk transfer.
      // First, detach _cxq.
      // The following loop is tantamount to: w = swap(&cxq, NULL)
      w = _cxq;
      for (;;) {
        assert(w != NULL, "Invariant");
        ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr(NULL, &_cxq, w);
        if (u == w) break;
        w = u;
      }
      assert(w != NULL, "invariant");

      ObjectWaiter * q = NULL;
      ObjectWaiter * p;
      for (p = w; p != NULL; p = p->_next) {
        guarantee(p->TState == ObjectWaiter::TS_CXQ, "Invariant");
        p->TState = ObjectWaiter::TS_ENTER;
        p->_prev = q;
        q = p;
      }

      // Prepend the RATs to the EntryList
      //将_EntryList连接在CXQ队列的末尾
      if (_EntryList != NULL) {
        q->_next = _EntryList;
        _EntryList->_prev = q;
      }
      //将_EntryList指向新的队列队首
      _EntryList = w;

      // Fall thru into code that tries to wake a successor from EntryList
    }
    //上面QMode = 3和QMode = 4的情况,都是链接一个新的队列,最后,_EntryList都指向队首。
    //当cxq为空的时候,就不用组成新队列,所以这里w直接等于_EntryList即可。
    w = _EntryList;
    //如果w != NULL:说明_EntryList一定不为空。
    if (w != NULL) {
      // I'd like to write: guarantee (w->_thread != Self).
      // But in practice an exiting thread may find itself on the EntryList.
      // Let's say thread T1 calls O.wait().  Wait() enqueues T1 on O's waitset and
      // then calls exit().  Exit release the lock by setting O._owner to NULL.
      // Let's say T1 then stalls.  T2 acquires O and calls O.notify().  The
      // notify() operation moves T1 from O's waitset to O's EntryList. T2 then
      // release the lock "O".  T2 resumes immediately after the ST of null into
      // _owner, above.  T2 notices that the EntryList is populated, so it
      // reacquires the lock and then finds itself on the EntryList.
      // Given all that, we have to tolerate the circumstance where "w" is
      // associated with Self.
      assert(w->TState == ObjectWaiter::TS_ENTER, "invariant");
      ExitEpilog(Self, w);
      return;
    }
    //执行到这儿,说明_EntryList一定为空。但是CXQ不一定为空,因为QMode可能不等于3或者4。

    // If we find that both _cxq and EntryList are null then just
    // re-run the exit protocol from the top.
    //判断CXQ是否为空
    w = _cxq;
    //如果CXQ也是空,则conntinue,continue后应该还记得前面有释放owner后判断cxq和entryList的,从哪儿直接就退出了。
    if (w == NULL) continue;

    // Drain _cxq into EntryList - bulk transfer.
    // First, detach _cxq.
    // The following loop is tantamount to: w = swap(&cxq, NULL)
    //执行到这儿说明CXQ肯定是不为空的,说明QMode不等于3或者4
    //将cxq赋值为空
    for (;;) {
      assert(w != NULL, "Invariant");
      ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr(NULL, &_cxq, w);
      if (u == w) break;
      w = u;
    }
    TEVENT(Inflated exit - drain cxq into EntryList);

    assert(w != NULL, "invariant");
    assert(_EntryList == NULL, "invariant");

    // Convert the LIFO SLL anchored by _cxq into a DLL.
    // The list reorganization step operates in O(LENGTH(w)) time.
    // It's critical that this step operate quickly as
    // "Self" still holds the outer-lock, restricting parallelism
    // and effectively lengthening the critical section.
    // Invariant: s chases t chases u.
    // TODO-FIXME: consider changing EntryList from a DLL to a CDLL so
    // we have faster access to the tail.

    if (QMode == 1) {
      // QMode == 1 : drain cxq to EntryList, reversing order
      // We also reverse the order of the list.
      ObjectWaiter * s = NULL;
      ObjectWaiter * t = w;
      ObjectWaiter * u = NULL;
      //将CXQ队列转换成双向队列。并且将_EntryList进行了反转(颠倒顺序)
      while (t != NULL) {
        guarantee(t->TState == ObjectWaiter::TS_CXQ, "invariant");
        t->TState = ObjectWaiter::TS_ENTER;
        u = t->_next;
        t->_prev = u;
        t->_next = s;
        s = t;
        t = u;
      }
      _EntryList  = s;
      assert(s != NULL, "invariant");
    } else {
      // QMode == 0 or QMode == 2
      //这种情况,也就是把cxq变成了双向队列。_EntryList指向队首。
      _EntryList = w;
      ObjectWaiter * q = NULL;
      ObjectWaiter * p;
      for (p = w; p != NULL; p = p->_next) {
        guarantee(p->TState == ObjectWaiter::TS_CXQ, "Invariant");
        p->TState = ObjectWaiter::TS_ENTER;
        p->_prev = q;
        q = p;
      }
    }

    // In 1-0 mode we need: ST EntryList; MEMBAR #storestore; ST _owner = NULL
    // The MEMBAR is satisfied by the release_store() operation in ExitEpilog().

    // See if we can abdicate to a spinner instead of waking a thread.
    // A primary goal of the implementation is to reduce the
    // context-switch rate.
    if (_succ != NULL) continue;

    w = _EntryList;
    if (w != NULL) {
      guarantee(w->TState == ObjectWaiter::TS_ENTER, "invariant");
      ExitEpilog(Self, w);
      return;
    }
  }
}

方法内容较多,单整体逻辑不难理解:
1、首先判断owner是否是当前线程,因为只有获得当前锁的线程,才能执行exit。
2、对重入次数进行减1操作。
3、接下来是个无限循环,只有成功释放了锁,才会跳出循环。在循环中,主要做的事情是:
(1)首先根据不同的退出策略执行了一段不同的逻辑,但是大致做的事情都差不多。只是Knob_ExitPolicy != 0的时候,先判断了cxq队列和_EntryList如果都是空才进行提前释放锁操作。这里提前对锁进行了释放,然后判断了cxq队列和_EntryList是否都是空,如果是,就直接返回了,因为没有等待锁的线程需要去唤醒。如果不为空,就会继续执行,开始唤醒等待在cxq或者_EntryList上的一个线程。但是要操作cxq和_EntryList必须要重新获得当前锁,所以这里进行了锁的重新获取,但是如果这里获取锁失败了,说明锁已经被其他线程抢占了,就直接返回了。
(2)因为唤醒一个CXQ和_EntryList上的等待锁的线程,具体应该唤醒哪一个呢?所以这里通过不同的策略,即QModel的不同,进行了不同的唤醒线程的选择。
QModel = 2: 这种情况,直接从CXQ的头部取出一个线程,然后执行ExitEpilog进行唤醒,ExitEpilog其实就做了两件事,一是将owner设置为NULL,二是通过unpark唤醒线程,后面能看到源码。
QModel = 3: 这种情况,把CXQ转换成了一个双向队列,然后连接在_EntryList的末尾。
QModel = 4: 这种情况,把CXQ转换成了一个双向队列,然后连接在_EntryList的头部。
上面这三种方式,必须是CXQ不为空的情况下才会执行,也只有CXQ不为空才有执行的必要,其中,第二种方式,如果CXQ不为空,直接执行唤醒然后返回了。
3,4两种情况下,会得到一个新的EntryList队列,然后继续往下执行。
继续往下执行w = _EntryList,这一步操作,如果QModel =3 或者 QModel = 4,则w指向新的队列,如果QModel既不是3也不是4,w就指向原本的EntryList。
所以如果W != NULL,说明Entry一定不等于NULL,CXQ是否为NULL是不确定的,这种情况下,只有QModel = 4才会首先唤醒原CXQ队列头。其他情况,都会优先唤醒_EntryList的的头节点。
如果W == NULL,说明EntryList一定是NULL,CXQ不一定是NULL。所以首先就判断CXQ是否是NULL,如果是,说明EntryList和CXQ都是NULL,就没必要继续执行唤醒了,直接continue重新判断,根据前面的判断会退出循环。
QModel = 1: 这种情况,将CXQ转换成双向队列,然后将_EntryList指向新的CXQ尾部(相当于将CXQ队列反转)。
QModel = 0: 这种情况,将CXQ转换成双向队列,将_EntryList指向新的CXQ头部。
执行完QModel = 1和QModel = 0的逻辑后,最后唤醒_EntryList执行的第一个线程。
JVM默认的QModel = 0。

重量级锁的释放过程如下:

在这里插入图片描述
关于Wait/Notify/NotifyAll的实现,请看Wait与notify/notifyAll源码分析

参考:
<<深入理解JVM虚拟机>>
https://createchance.github.io/post/java-并发之基石篇/
https://github.com/farmerjohngit/myblog/issues/13

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值