Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程

前言

线程并发系列文章:

Java 线程基础
Java “优雅”地中断线程
Java 线程状态
真正理解Java Volatile的妙用
Java ThreadLocal你之前了解的可能有误
Java Unsafe/CAS/LockSupport 应用与原理
Java 并发"锁"的本质(一步步实现锁)
Java Synchronized实现互斥之应用与源码初探
Java 对象头分析与使用(Synchronized相关)
Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程
Java Synchronized 重量级锁原理深入剖析上(互斥篇)
Java Synchronized 重量级锁原理深入剖析下(同步篇)
Java并发之 AQS 深入解析(上)
Java并发之 AQS 深入解析(下)
Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 详解
Java 并发之 ReentrantLock 深入分析(与Synchronized区别)
Java 并发之 ReentrantReadWriteLock 深入分析
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(原理篇)
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇)
最详细的图文解析Java各种锁(终极篇)

上篇文章已经分析了Java对象头构成、源码及其对象头的调试,本篇将分析偏向锁、轻量级锁、重量级锁的实现及其演变过程。由于涉及到c++源码,估计不少同学没兴趣看,因此重点多以图+源码辅助分析。
通过本篇文章,你将了解到:

1、什么是重量级锁
2、轻量级锁/偏向锁的由来
3、偏向锁的加锁、撤销锁、释放锁
4、轻量级锁的加锁、释放锁
5、偏向锁、轻量级锁、重量级锁的异同点

1、什么是重量级锁

简单例子

    private synchronized void testLock() {
        //doSomething();
    }

现有两个线程(t1、t2)同时访问testLock()方法,假若t1先拿到锁并执行同步块里的代码。此时t2也要访问testLock()方法,但是因为锁被t1持有,因此t2只能阻塞等待t1释放锁。
此时,锁的形态称为重量锁。

重量级锁为什么"重"

由上面的例子可以看出,t2因为没有获取到锁然后挂起自己,等待t1释放锁后唤醒自己。线程的挂起/唤醒需要CPU切换上下文,此过程代价比较大,因此称此种锁为重量级锁。
线程挂起/唤醒请移步:Java Unsafe/CAS/LockSupport 应用与原理

2、轻量级锁/偏向锁的由来

轻量级锁的由来

还是上面的例子,假设现在t1、t2是交替执行testLock()方法,此时t1、t2没必要阻塞,因为它们之间没有竞争,也就是不需要重量级锁。
线程之间交替执行临界区的情形下使用的锁称为轻量级锁。

轻量级锁相比重量级锁的优势:

1、每次加锁只需要一次CAS
2、不需要分配ObjectMonitor对象
3、线程无需挂起与唤醒

偏向锁的由来

依旧是上面的例子,假设testLock()始终只有一个线程t1在执行呢?这个时候若是使用轻量级锁,每次t1获取锁都需要进行一次CAS,有点浪费性能。
因此就出现了偏向锁:

当锁偏向某个线程时,该线程再次获取锁时无需CAS,只需要一个简单的比较就可以获取锁,这个过程效率很高。

偏向锁相比轻量级锁的优势:

同一个线程多次获取锁时,无需再次进行CAS,只需要简单比较。

3、偏向锁的加锁、撤销锁、释放锁

上面阐述了这三种锁的由来,这些锁是如何实现的呢?接下来从源码的角度进行分析。这三种锁的基础是对象头,关于对象头的详细分析请查看:Java 对象头分析与使用(Synchronized相关)

锁的本质是共享变量,因此问题的关键是如何访问这个共享变量。理解这个对于理解三种锁的演变事半功倍,接下来将重点突出这一信息。
既然涉及到了锁,那么自然而然有加锁/释放锁操作,偏向锁比较特殊还多了个撤销锁的操作。

加锁

先来复习对象头:


Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程_第1张图片
image.png

可以看到偏向锁里存储了偏向线程的id,epoch,偏向锁标记(biased_lock),锁标记(lock)等信息。这些信息统称为Mark Word。
在看源码之前,先来朴素(脑补)地猜测线程t1获取偏向锁的过程:

1、先判断Mark Word里的线程id是否有值。
1.1、如果没有,说明还没有线程占用锁,则直接将t1的线程id记录到Mark Word里。可能会存在多个线程同时修改Mark Word,因此需要进行CAS修改Mark Word。
1.2、如果已有id值,那么判断分两种情况:
1.2.1、该id是t1的id,则此次获取锁是个重入的过程,直接就获取了。
1.2.2、如果该id不是t1的id,说明已经有其它线程获取了锁,t1想要获取锁就需要走撤销流程。

来看看源码:bytecodeInterpreter.cpp

      CASE(_monitorenter): {
        //获取对象头,用oop表示对象头 -------->(1)
        oop lockee = STACK_OBJECT(-1);
        CHECK_NULL(lockee);
        BasicObjectLock* limit = istate->monitor_base();
        BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
        BasicObjectLock* entry = NULL;
        while (most_recent != limit ) {
          //遍历线程栈,找到对应的空闲的BasicObjectLock (2)
          if (most_recent->obj() == NULL) entry = most_recent;
          else if (most_recent->obj() == lockee) break;
          most_recent++;
        }
        if (entry != NULL) {
          //BasicObjectLock _obj字段指向oop ------>(3)
          entry->set_obj(lockee);
          int success = false;
          uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;

          //取出对象头里的Mark Word
          markOop mark = lockee->mark();
          intptr_t hash = (intptr_t) markOopDesc::no_hash;
          // 支持偏向锁
          if (mark->has_bias_pattern()) {
            uintptr_t thread_ident;
            uintptr_t anticipated_bias_locking_value;
            //当前的线程id
            thread_ident = (uintptr_t)istate->thread();
            //异或运算结果-------->(4)
            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) {
              //完全相等,则认为是重入了该锁------>(5)
              if (PrintBiasedLockingStatistics) {
                (* BiasedLocking::biased_lock_entry_count_addr())++;
              }
              success = true;
            }
            else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
              //不支持偏向锁了-------->(6)
              //构造无锁的Mark Word
              markOop header = lockee->klass()->prototype_header();
              if (hash != markOopDesc::no_hash) {
                header = header->copy_set_hash(hash);
              }
              //CAS 修改Mark Word为无锁状态
              if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) {
                if (PrintBiasedLockingStatistics)
                  (*BiasedLocking::revoked_lock_entry_count_addr())++;
              }
            }
            else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
              //epoch 过期了------->(7)
              //使用当前线程id构造偏向锁
              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修改
              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 {
              //构造匿名偏向锁---------(8)
              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 修改为偏向当前线程的锁
              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;
            }
          }

          if (!success) {
            //上面尝试使用偏向锁,可惜没有成功,则尝试升级为轻量级锁
            markOop displaced = lockee->mark()->set_unlocked();
            entry->lock()->set_displaced_header(displaced);
            bool call_vm = UseHeavyMonitors;
            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())) {
                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)~(9)个点:
(1)
oop 表示对象头,里边包括含了Mark Word、Klass Word。

(2)
在basicLock.hpp里,BasicObjectLock 结构如下:

#basicLock.hpp
class BasicObjectLock VALUE_OBJ_CLASS_SPEC {
  friend class VMStructs;
 private:
  //BasicLock
  BasicLock _lock;                                  
  //对象头
  oop       _obj;                                   
  ...
};

继续看BasicLock:

#basicLock.hpp
class BasicLock VALUE_OBJ_CLASS_SPEC {
  friend class VMStructs;
 private:
  //存储Mark Word
  volatile markOop _displaced_header;
  ...
};

BasicObjectLock 即是熟知的Lock Record的实现,其包含了两个内容:

1、存储Mark Word的_displaced_header
2、指向对象头的指针:_obj

Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程_第2张图片
image.png

如图,线程的私有栈里存储着多个Lock Record。

(3)
将Lock Record里的_obj赋值为lockee,也就是_obj表示的是对象头。

(4)
从对象头(lockee)里取出Klass Word,该Word是指向Klass类型的指针,Klass类里有个名为_prototype_header字段, 也是表示Mark Word,里面存储着epoch、偏向锁标记等信息(后面为方便描述,使用Klass替代说明)。此处是取出这些信息并拼接上当前线程id,进而和对象头里的Mark Word进行异或运算,找出不相等的位,接下来就是判断具体Mark Word的哪个部分不相等,从而有不同的处理逻辑。

(5)
如果上面的异或相等,那说明Mark Word里存储有当前线程id,epoch、偏向锁标记都一致,也就是锁被当前线程持有了,此次是个重入的过程。因为已经拥有锁了,所以啥也不干了。

(6)
发现Mark Word里的偏向锁标志位和Klass 里的不同,而Mark Word之前已经判断是偏向锁了,因此可以推断Klass 已经不支持偏向锁了。既然不支持偏向锁了,就修改Mark Word为无锁状态,等待后面升级为轻量级锁/重量级锁。

(7)
发现Mark Word里的epoch与Klass里的不同,则认为发生了批量重偏向,因此可以直接修改Mark Word偏向当前线程。

(8)
如果上述条件都不满足,则认为当前是匿名偏向锁(是偏向锁,但是没有偏向任何线程)。尝试直接修改Mark Word偏向当前线程。

通过上述步骤的分析,结果比较明显了:

1、线程每次尝试获取锁都需要关联Lock Record,并将_obj指向对象头,此时Lock Record与对象头就建立了联系。
2、线程成功将线程id写入Mark Word后即表示该线程获取了该偏向锁

偏向锁状态时Lock Record与对象头关系:


Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程_第3张图片
image.png

此时_displaced_header字段并没有使用。

用图表示偏向锁获取流程:


Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程_第4张图片
image.png

好了,再来回顾一下线程t1、t2获取偏向锁的过程:

1、t1获取锁,一开始锁是匿名偏向锁,所以走的是上图步骤4,成功获取锁。
2、t1再次获取锁,因为之前已经获取到锁了,所以走的是上图步骤1,重入获取锁。
3、此时t2尝试获取锁,因为t1正在持有锁,因此走的是上图步骤5。

1、4、5 步骤情景已经涉及到了,剩下2、3步骤下面分析。

撤销锁

偏向锁获取不成功,那么在升级为轻量级锁之前先将锁变为无锁状态,此为偏向锁的撤销过程。
来看看源码入口:

#InterpreterRuntime.cpp
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
  ...
  if (UseBiasedLocking) {
    //使用偏向锁则进入快速处理流程
    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
  } else {
    //升级为轻量级锁
    ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
  }
  ...

#synchronizer.cpp
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 {
      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) ;
}

可以看出撤销分为在安全点撤销和非安全点撤销。
重点说一下非安全点撤销:revoke_and_rebias
里面代码比较多,就不一一贴出了。
用图表示如下:


Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程_第5张图片
image.png

上面的撤销是在不安全点执行的,因此都会有CAS操作。
上图进行了初步的撤销/重偏向,若是成功则后续会升级为轻量级锁;若是失败,则需要进一步地撤销。


Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程_第6张图片
image.png

批量重偏向/批量撤销逻辑最终也会调用直接撤销函数,继续来看看直接撤销的流程,实际上就是biasedLocking.cpp#revoke_bias 函数:


Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程_第7张图片
image.png

可以看出,上图修改Mark Word时并没有使用CAS,因为执行这段代码是在安全点执行的,也就是说只要执行了就能成功。

批量重偏向与批量撤销

经过上面的分析,我们知道:

1、当某个线程持有偏向锁,另一个线程想要获取锁时需要撤销锁。
2、撤销先尝试在不安全点使用CAS修改Mark Word为无锁状态,若还是无法撤销则考虑在安全点撤销,等安全点是比较低效的操作。

因此偏向锁引入了批量重偏向与批量撤销。
当某个类的对象锁撤销次数达到一定阈值,比如达到了20次,那么就会触发批量重偏向的逻辑,修改Klass里的epoch值,并修改当前正在使用该类型锁Mark Word里的epoch值。当线程想要获取偏向锁时,对比当前对象的epoch值与Klass里的epoch值,发现不相等,则认为过期。此时该线程被允许直接CAS修改Mark Word偏向当前线程,就不用再走撤销逻辑了。这部分对应最初分析偏向锁入口的标记(7)。
同样的当撤销次数达到40次时,认为该对象已经不适合应用偏向锁了,因此会修改Klass里的偏向锁标记,更改为不支持偏向锁。当线程想要获取偏向锁时,检查Klass里的偏向锁标记值,若是不允许偏向,说明之前发生了批量撤销,因此该线程被允许直接CAS修改Mark Word为无锁状态,就不用再走撤销逻辑了。这部分对应最初分析偏向锁入口的标记(6)。

默认的阈值:


Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程_第8张图片
image.png

批量重偏向与批量撤销是对偏向锁性能的优化。

释放锁

正常的想法是:当线程退出临界区,也就是释放了锁。

#bytecodeInterpreter.cpp
      CASE(_monitorexit): {
        //对象头
        oop lockee = STACK_OBJECT(-1);
        CHECK_NULL(lockee);
        BasicObjectLock* limit = istate->monitor_base();
        BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
        //遍历线程栈
        while (most_recent != limit ) {
          //找到对应的Lock Record
          if ((most_recent)->obj() == lockee) {
            BasicLock* lock = most_recent->lock();
            markOop header = lock->displaced_header();
            //设置Lock Record 里的_obj字段 为null
            most_recent->set_obj(NULL);
            //此处是轻量级锁的释放,先省略
            UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
          }
          most_recent++;
        }
        ...
      }

偏向锁对象头和Lock Record关系前面已经分析:每次尝试获取偏向锁时,先找到空闲的Lock Record,并将Lock Record里的_obj指向对象头,表示这俩建立起了联系。释放锁的时候将这联系切断,_obj=null。
你也许已经发现了Mark Word并没有发生改变,依然是偏向了之前的线程,那还是没释放锁的嘛。的确是,线程退出临界区时候,并没有释放偏向锁,这么做的目的是:

当再次需要获取锁的时候,只需要简单按位运算判断是否是重入,即可快速获取锁,而不用每次都CAS,这也是偏向锁在只有一个线程访问锁的情景下高效的核心所在。

小结

前边花了很大篇幅阐述偏向锁,看起来很复杂,实际上就是撤销部分比较复杂。

1、偏向锁的"锁"即是Mark Word,想要获取锁就需要对Mark Word进行修改,可能会有多线程竞争修改,因此需要借助CAS。
2、因为撤销操作可能需要在安全点执行,效率比较低,多次撤销更会影响效率,因此引入了批量重偏向与批量撤销。
3、偏向锁的重入计数依靠线程栈里Lock Record个数。
4、偏向锁撤销失败,最终会升级为轻量级锁。
5、偏向锁退出时并没有修改Mark Word,也就是没有释放锁。

4、轻量级锁的加锁、释放锁

加锁

偏向锁的撤销操作比较复杂,而轻量级锁的加锁、释放锁则简单得多。


#synchronizer.cpp
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
  //取出Mark Word
  markOop mark = obj->mark();
  //走到此说明已经不是偏向锁了
  assert(!mark->has_bias_pattern(), "should not see bias pattern here");

  if (mark->is_neutral()) {
    //如果是无锁状态
    //将Mark Word拷贝到Lock Record的_displaced_header 字段里
    lock->set_displaced_header(mark);
    //CAS修改Mark Word使之指向Lock Record
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
      TEVENT (slow_enter: release stacklock) ;
      return ;
    }
  } else
  if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
    //mark->has_locker() -->表示已经是轻量级锁
    //THREAD->is_lock_owned((address)mark->locker()) 并且是当前线程获取了轻量级锁
    //这两点说明当前线程重入该锁
    //直接设置header==null
    lock->set_displaced_header(NULL);
    return;
  }
  //走到这说明不能使用轻量级锁,则需要升级为重量级锁

此时,我们发现Lock Record与轻量级锁的关系更加紧密。


Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程_第9张图片
image.png

偏向锁了没用的_displaced_header用上了,用以存储无锁状态的Mark Word,待释放锁时恢复(保留了hash值等)。
而Mark Word里的锁记录指针指向了Lock Record,表示该Lock Record所在的线程获取了轻量级锁。

释放锁

      #bytecodeInterpreter.cpp
      CASE(_monitorexit): {
        oop lockee = STACK_OBJECT(-1);
        CHECK_NULL(lockee);
        BasicObjectLock* limit = istate->monitor_base();
        BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
        while (most_recent != limit ) {
          if ((most_recent)->obj() == lockee) {
            BasicLock* lock = most_recent->lock();
            markOop header = lock->displaced_header();
            most_recent->set_obj(NULL);
            //以上部分和偏向锁释放一致的

            if (!lockee->mark()->has_bias_pattern()) {
              //不是偏向模式
              bool call_vm = UseHeavyMonitors;
              //header 不为空,说明是线程第一次获取轻量级锁时占用的Lock Record
              if (header != NULL || call_vm) {
                //而header存储的是无锁状态的Mark Word
                //因此需要将Mark Word修改恢复为之前的无锁状态
                if (call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {
                  //失败的话,再将obj设置上,为了重量级锁使用
                  most_recent->set_obj(lockee);
                  CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);
                }
              }
            }
            UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
          }
          most_recent++;
        }
        ...
      }

与偏向锁不同的是,轻量级锁是真的释放了锁,因为修改Mark Word为无锁状态了。
你可能会有疑惑:只有拿到锁的线程才会有释放锁的操作,为什么此处还需要CAS呢?

考虑一种情况:线程A获取了轻量级锁,此时线程B也想要获取锁,由于锁被A占用,因此B将锁膨胀为重量级锁(修改了Mark Word)。而此时A还在执行临界区代码,它并不知道Mark Word已经被更改了。所以当A退出临界区释放锁的时候,它不能直接修改Mark Word,于是使用CAS尝试更新Mark Word。若是Mark Word已经改变了,也就是说之前Mark Word是指向线程A的Lock Reocrd指针,现在是指向ObjectMonitor了,当然A的CAS会失败,接着进行下一步判断,最终可能膨胀为重量级锁。若是没有改变,A释放轻量级锁就直接成功了。

小结

1、轻量级锁的"锁"即是Mark Word,想要获取锁就需要对Mark Word进行修改,可能会有多线程竞争修改,因此需要借助CAS。
2、如果初始锁为无锁状态,则每次进入都需要一次CAS尝试修改为轻量级锁,否则判断是否重入。
3、如果不满足2的条件,则膨胀为重量级锁。
4、轻量级锁退出时即释放锁,变为无锁状态。
5、可以看出轻量级锁比较敏感,一旦有线程竞争就会膨胀为重量级锁。

5、偏向锁、轻量级锁、重量级锁的异同点

由上面的分析可知,想要在不安全点获取锁,就得依靠CAS操作,因此理解CAS的原理是深入锁的基础。有关CAS原理与使用请移步:Java Unsafe/CAS/LockSupport 应用与原理

三者的共同点:

1、都需要和Lock Record关联;偏向锁和重量级锁只用到了_obj字段,而轻量级锁用到了_displaced_header。
2、释放锁时都需要修改Lock Record 里的_obj字段。

三者不同点:

1、偏向锁和轻量级锁的"锁"即是Mark Word,而重量级锁的"锁"是ObjectMonitor,此时Mark Word保留了指针指向ObjectMonitor。
2、偏向锁和轻量级锁依靠Lock Record个数来记录重入的次数,而重量级锁通过
ObjectMonitor里的_recursions 整形变量记录。
3、偏向锁和轻量级锁的重入只需要做简单的判断即可,而重量级锁需要通过CAS判断是否是重入。

三者适用场景

1、偏向锁适合在只有一个线程访问锁的场景,在此种场景下,线程只需要执行一次CAS获取偏向锁,后续该线程再次访问该锁时仅仅只需要简单的判断即可获取锁。
2、轻量级锁适合在有多个线程交替访问锁,并且不会发生竞争的场景。此种场景下,线程每次获取锁只需要执行一次CAS即可。
3、重量级锁适合在多线程竞争环境下访问锁,执行临界区的时间比较长,未获取锁的线程将会被挂起,等待拥有锁的线程释放锁而后唤醒它。此种场景下,线程每次都需要进行多次CAS操作,操作失败将会被放入队列里等待唤醒。

值得注意的是:

偏向锁、轻量级锁是在Java1.6(Java 6)提出的用以对重量级锁的改进。
从上面分析我们也知道为了实现偏向锁的撤销,引入了复杂的同步代码,包括在安全点执行等操作,且对 HotSpot 的其他组件产生了影响。这种复杂性已经成为理解代码的障碍,也阻碍了对同步系统进行重构。因此,在Java 15废弃了偏向锁。https://openjdk.java.net/jeps/374。

至此,偏向锁、轻量级锁的原理已经阐述完毕,由于篇幅所限,重量级锁的原理下篇分析。

虽然尽量避免贴过多的代码,但还是无法避免贴了一些,不关注源码的同学请直接看每段的小结。若是对更多的源码细节感兴趣,可查看下面的链接,本篇也参考了以下链接:
https://github.com/farmerjohngit/myblog/issues/13
https://github.com/HenryChenV/my-notes/issues/3

本文源码基于jdk1.8。

您若喜欢,请点赞、关注,您的鼓励是我前进的动力

持续更新中,和我一起步步为营系统、深入学习Android

1、Android各种Context的前世今生
2、Android DecorView 一窥全貌(上)
3、Android DecorView 一窥全貌(下)
4、Window/WindowManager 不可不知之事
5、View Measure/Layout/Draw 真明白了
6、Android事件分发全套服务
7、Android invalidate/postInvalidate/requestLayout 彻底厘清
8、Android Window 如何确定大小/onMeasure()多次执行原因
9、Android事件驱动Handler-Message-Looper解析
10、Android 键盘一招搞定
11、Android 各种坐标彻底明了
12、Android Activity/Window/View 的background
13、Android IPC 之Service 还可以这么理解
14、Android IPC 之Binder基础
15、Android IPC 之Binder应用
16、Android IPC 之AIDL应用(上)
17、Android IPC 之AIDL应用(下)
18、Android IPC 之Messenger 原理及应用
19、Android IPC 之获取服务(IBinder)
20、Android 存储基础
21、Android 10、11 存储完全适配(上)
22、Android 10、11 存储完全适配(下)
23、Java 并发系列不再疑惑

你可能感兴趣的:(Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程)