美文网首页
Sychronized关键字使用及原理解析

Sychronized关键字使用及原理解析

作者: justonemoretry | 来源:发表于2022-05-22 22:56 被阅读0次

    Sychronized关键字使用场景

    三种方法

    修饰实例方法
    作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。

    synchronized void method() {
      //业务代码
    }
    

    修饰静态方法
    给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的锁。因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管 new 了多少个对象,只有一份)。
    所以,如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。

    synchronized static void method() {
    //业务代码
    }
    

    修饰代码块
    指定加锁对象,对给定对象/类加锁。synchronized(this|object) 表示进入同步代码库前要获得给定对象的锁。synchronized(类.class) 表示进入同步代码前要获得 当前 class 的锁。

    synchronized(this) {
      //业务代码
    }
    

    总结

    • synchronized 关键字加到 static 静态方法和 synchronized(class) 代码块上都是是给 Class 类上锁。
    • synchronized 关键字加到实例方法上是给对象实例上锁。
    • 尽量不要使用 synchronized(String a) 因为 JVM 中,字符串常量池具有缓存功能!

    Sychroniezd 关键字实现原理

    对象头

    当线程访问同步块时首先需要获得锁并把相关信息存储在对象头中。所以 wait、notify、notifyAll 这些方法为什么被设计在 Object 中或许你已经找到答案了。
    Hotspot 有两种对象头:

    • 数组类型,使用 arrayOopDesc 来描述对象头
    • 其它,使用 instanceOopDesc 来描述对象头

    对象头由两部分组成:

    • Mark Word:存储自身的运行时数据,例如 HashCode、GC 年龄、锁相关信息等内容。
    • Klass Pointer:类型指针指向它的类元数据的指针。

    64 位虚拟机 Mark Word 是 64bit 其结构如下:

    image.png
    在 JDK 6 中虚拟机团队对锁进行了重要改进,优化了其性能引入了 偏向锁、轻量级锁、适应性自旋、锁消除、锁粗化等实现。

    总体上来说锁状态升级流程如下:


    image.png

    偏向锁

    流程

    HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
    偏向锁的撤销
    偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。

    image.png
    关闭偏向锁
    偏向锁在Java 6和Java 7里是默认启用的,但是它在应用程序启动几秒钟之后才激活,如有必要可以使用JVM参数来关闭延迟:-XX:BiasedLockingStartupDelay=0。如果你确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。

    轻量级锁

    轻量级加锁
    线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced MarkWord。
    拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock Record里的owner指针指向对象的Mark Word。
    如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,表示此对象处于轻量级锁定状态。
    如果轻量级锁的更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明多个线程竞争锁。
    若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。
    轻量级解锁
    轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。图2-2是两个线程同时争夺锁,导致锁膨胀的流程图。

    image.png
    因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。

    自旋锁 vs 适应性自旋锁

    阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。
    在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁。
    而为了让当前线程“稍等一下”,我们需让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。


    image.png

    自旋锁本身是有缺点的,它不能代替阻塞。自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当挂起线程。
    自旋锁在JDK1.4.2中引入,使用-XX:+UseSpinning来开启。JDK 6中变为默认开启,并且引入了自适应的自旋锁(适应性自旋锁)。
    自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

    重量级锁

    Monitor可以理解为一个同步工具或一种同步机制,通常被描述为一个对象。每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁。
    Monitor是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联,同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。
    现在话题回到synchronized,synchronized通过Monitor来实现线程同步,Monitor是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的线程同步。
    如同我们在自旋锁中提到的“阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长”。这种方式就是synchronized最初实现同步的方式,这就是JDK 6之前synchronized效率低的原因。这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”,JDK 6中为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。
    重量级锁中队列
    ObjectMonitor 中包含一个同步队列(由 _cxq 和 _EntryList 组成)一个等待队列( _WaitSet )。
    被notify或 notifyAll 唤醒时根据 policy 策略选择加入的队列(policy 默认为 0)
    退出同步块时根据 QMode 策略来唤醒下一个线程(QMode 默认为 0)。
    synchronized 的 monitor锁机制和 JDK 并发包中的 AQS 是很相似的,只不过 AQS 中是一个同步队列多个等待队列。熟悉 AQS 的同学可以拿来做个对比。
    队列协作流程图

    image.png

    源码解析

    在 HotSpot 中 monitor 是由 ObjectMonitor 实现的。其源码是用 c++来实现的源文件是 ObjectMonitor.hpp 主要数据结构如下所示:

    ObjectMonitor() {
        _header       = NULL;
        _count        = 0;
        _waiters      = 0,       // 等待中的线程数
        _recursions   = 0;       // 线程重入次数
        _object       = NULL;    // 存储该 monitor 的对象
        _owner        = NULL;    // 指向拥有该 monitor 的线程
        _WaitSet      = NULL;    // 等待线程 双向循环链表_WaitSet 指向第一个节点
        _WaitSetLock  = 0 ;
        _Responsible  = NULL ;
        _succ         = NULL ;
        _cxq          = NULL ;   // 多线程竞争锁时的单向链表
        FreeNext      = NULL ;
        _EntryList    = NULL ;   // _owner 从该双向循环链表中唤醒线程,
        _SpinFreq     = 0 ;
        _SpinClock    = 0 ;
        OwnerIsThread = 0 ;
        _previous_owner_tid = 0; // 前一个拥有此监视器的线程 ID
      }
    
    1. 初始时为 NULL。当有线程占有该 monitor 时 owner 标记为该线程的 ID。当线程释放 monitor 时 owner 恢复为 NULL。owner 是一个临界资源 JVM 是通过 CAS 操作来保证其线程安全的。
    2. _cxq:竞争队列所有请求锁的线程首先会被放在这个队列中(单向)。_cxq 是一个临界资源 JVM 通过 CAS 原子指令来修改_cxq 队列。
      每当有新来的节点入队,它的 next 指针总是指向之前队列的头节点,而_cxq 指针会指向该新入队的节点,所以是后来居上。
    3. _EntryList: _cxq 队列中有资格成为候选资源的线程会被移动到该队列中。
    4. _WaitSet: 等待队列因为调用 wait 方法而被阻塞的线程会被放在该队列中。
      monitor竞争操作
    1. 通过 CAS 尝试把 monitor 的 owner 字段设置为当前线程。
    2. 如果设置之前的 owner 指向当前线程,说明当前线程再次进入 monitor,即 重入锁执行 recursions ++ , 记录重入的次数。
    3. 如果当前线程是第一次进入该 monitor, 设置 recursions 为 1,_owner 为当前线程,该线程成功获得锁并返回。
    4. 如果获取锁失败,则等待锁的释放。

    执行 monitorenter 指令时 调用以下代码:

    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");
    // 是否使用偏向锁  JVM 启动时设置的偏向锁-XX:-UseBiasedLocking=false/true
      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);
      }
      assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
             "must be NULL or an object");
    #ifdef ASSERT
      thread->last_frame().interpreter_frame_verify_monitor(elem);
    #endif
    IRT_END
    

    slow_enter 方法主要是轻量级锁的一些操作,如果操作失败则会膨胀为重量级锁,过程前面已经描述比较清楚此处不在赘述。enter 方法则为重量级锁的入口源码如下:

    void ATTR ObjectMonitor::enter(TRAPS) {
      Thread * const Self = THREAD ;
      void * cur ;
      // 省略部分代码
      
      // 通过 CAS 操作尝试把 monitor 的_owner 字段设置为当前线程
      cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
      if (cur == NULL) {
         assert (_recursions == 0   , "invariant") ;
         assert (_owner      == Self, "invariant") ;
         return ;
      }
    
     // 线程重入,recursions++
      if (cur == Self) {
         _recursions ++ ;
         return ;
      }
    
        // 如果当前线程是第一次进入该 monitor, 设置_recursions 为 1,_owner 为当前线程
      if (Self->is_lock_owned ((address)cur)) {
        assert (_recursions == 0, "internal state error");
        _recursions = 1 ;
        _owner = Self ;
        OwnerIsThread = 1 ;
        return ;
      }
    
        for (;;) {
          jt->set_suspend_equivalent();
            // 如果获取锁失败,则等待锁的释放;
          EnterI (THREAD) ;
    
          if (!ExitSuspendEquivalent(jt)) break ;
              _recursions = 0 ;
          _succ = NULL ;
          exit (false, Self) ;
    
          jt->java_suspend_self();
        }
        Self->set_current_pending_monitor(NULL);
      }
    }
    

    monitor等待

    1. 当前线程被封装成 ObjectWaiter 对象 node,状态设置成 ObjectWaiter::TS_CXQ。
    2. for 循环通过 CAS 把 node 节点 push 到_cxq列表中,同一时刻可能有多个线程把自己的 node 节点 push 到_cxq列表中。
    3. node 节点 push 到_cxq 列表之后,通过自旋尝试获取锁,如果还是没有获取到锁则通过 park 将当前线程挂起等待被唤醒。
    4. 当该线程被唤醒时会从挂起的点继续执行,通过 ObjectMonitor::TryLock 尝试获取锁。
    // 省略部分代码
    void ATTR ObjectMonitor::EnterI (TRAPS) {
        Thread * Self = THREAD ;
        assert (Self->is_Java_thread(), "invariant") ;
        assert (((JavaThread *) Self)->thread_state() == _thread_blocked   , "invariant") ;
    
        // Try lock 尝试获取锁
        if (TryLock (Self) > 0) {
            assert (_succ != Self              , "invariant") ;
            assert (_owner == Self             , "invariant") ;
            assert (_Responsible != Self       , "invariant") ;
            // 如果获取成功则退出,避免 park unpark 系统调度的开销
            return ;
        }
    
        // 自旋获取锁
        if (TrySpin(Self) > 0) {
            assert (_owner == Self, "invariant");
            assert (_succ != Self, "invariant");
            assert (_Responsible != Self, "invariant");
            return;
        }
    
        // 当前线程被封装成 ObjectWaiter 对象 node, 状态设置成 ObjectWaiter::TS_CXQ
        ObjectWaiter node(Self) ;
        Self->_ParkEvent->reset() ;
        node._prev   = (ObjectWaiter *) 0xBAD ;
        node.TState  = ObjectWaiter::TS_CXQ ;
    
        // 通过 CAS 把 node 节点 push 到_cxq 列表中
        ObjectWaiter * nxt ;
        for (;;) {
            // node节点插在头部,置换为_cxq队列的头节点
            node._next = nxt = _cxq ;
            if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
    
            // 再次 tryLock
            if (TryLock (Self) > 0) {
                assert (_succ != Self         , "invariant") ;
                assert (_owner == Self        , "invariant") ;
                assert (_Responsible != Self  , "invariant") ;
                return ;
            }
        }
    
        for (;;) {
            // 本段代码的主要思想和 AQS 中相似可以类比来看
            // 再次尝试
            if (TryLock (Self) > 0) break ;
            assert (_owner != Self, "invariant") ;
    
            if ((SyncFlags & 2) && _Responsible == NULL) {
               Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
            }
    
            // 满足条件则 park self
            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 > 1000) RecheckInterval = 1000 ;
            } else {
                TEVENT (Inflated enter - park UNTIMED) ;
                // 通过 park 将当前线程挂起,等待被唤醒
                Self->_ParkEvent->park() ;
            }
    
            if (TryLock(Self) > 0) break ;
            // 再次尝试自旋
            if ((Knob_SpinAfterFutile & 1) && TrySpin(Self) > 0) break;
        }
        return ;
    }
    

    monitor释放
    当某个持有锁的线程执行完同步代码块时,会释放锁并 unpark 后续线程(由于篇幅只保留重要代码)。

    void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
       Thread * Self = THREAD ;
      
       if (_recursions != 0) {
         _recursions--;        // this is simple recursive enter
         TEVENT (Inflated exit - recursive) ;
         return ;
       }
    
          ObjectWaiter * w = NULL ;
          int QMode = Knob_QMode ;
    
        // 直接绕过 EntryList 队列,从 cxq 队列中获取线程用于竞争锁
          if (QMode == 2 && _cxq != NULL) {
              w = _cxq ;
              assert (w != NULL, "invariant") ;
              assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
              ExitEpilog (Self, w) ;
              return ;
          }
        // cxq 队列插入 EntryList 尾部
          if (QMode == 3 && _cxq != NULL) {
              w = _cxq ;
              // _cxq指向null
              for (;;) {
                 assert (w != NULL, "Invariant") ;
                 ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
                 if (u == w) break ;
                 w = u ;
              }
              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 ;
              }
    
              ObjectWaiter * Tail ;
              for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
              // 放到entryList列表尾部
              if (Tail == NULL) {
                  _EntryList = w ;
              } else {
                  Tail->_next = w ;
                  w->_prev = Tail ;
              }
          }
    
        // cxq 队列插入到_EntryList 头部
          if (QMode == 4 && _cxq != NULL) {
              // 把 cxq 队列放入 EntryList
              // 此策略确保最近运行的线程位于 EntryList 的头部
              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 ;
              }
    
              if (_EntryList != NULL) {
                  q->_next = _EntryList ;
                  _EntryList->_prev = q ;
              }
              _EntryList = w ;
          }
    
          w = _EntryList  ;
          if (w != NULL) {
              assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
              ExitEpilog (Self, w) ;
              return ;
          }
          w = _cxq ;
          if (w == NULL) continue ;
    
          for (;;) {
              assert (w != NULL, "Invariant") ;
              ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
              if (u == w) break ;
              w = u ;
          }
    
          if (QMode == 1) {
             // QMode == 1 : 把 cxq 倾倒入 EntryList 逆序
             // 单向链表改双向,且逆序。s代表前一个节点,u代表下一个节点
             ObjectWaiter * s = NULL ;
             ObjectWaiter * t = w ;
             ObjectWaiter * u = NULL ;
             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
             _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 ;
             }
          }
    
          if (_succ != NULL) continue;
    
          w = _EntryList  ;
          if (w != NULL) {
              guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
              ExitEpilog (Self, w) ;
              return ;
          }
       }
    }
    

    notify唤醒
    notify 或者 notifyAll 方法可以唤醒同一个锁监视器下调用 wait 挂起的线程,具体实现如下:

    void ObjectMonitor::notify(TRAPS) {
        CHECK_OWNER();
        if (_WaitSet == NULL) {
            TEVENT (Empty - Notify);
            return;
        }
        DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);
    
        int Policy = Knob_MoveNotifyee;
    
        Thread::SpinAcquire(&_WaitSetLock, "WaitSet - notify");
        ObjectWaiter *iterator = DequeueWaiter();
        if (iterator != NULL) {
            // 省略一些代码
    
             // 头插 EntryList
            if (Policy == 0) {
                if (List == NULL) {
                    iterator->_next = iterator->_prev = NULL;
                    _EntryList = iterator;
                } else {
                    List->_prev = iterator;
                    iterator->_next = List;
                    iterator->_prev = NULL;
                    _EntryList = iterator;
                }
            } else if (Policy == 1) {      // 尾插 EntryList
                if (List == NULL) {
                    iterator->_next = iterator->_prev = NULL;
                    _EntryList = iterator;
                } else {
                    ObjectWaiter *Tail;
                    for (Tail = List; Tail->_next != NULL; Tail = Tail->_next);
                    assert (Tail != NULL && Tail->_next == NULL, "invariant");
                    Tail->_next = iterator;
                    iterator->_prev = Tail;
                    iterator->_next = NULL;
                }
            } else if (Policy == 2) {      // 头插 cxq
                // prepend to cxq
                if (List == NULL) {
                    iterator->_next = iterator->_prev = NULL;
                    _EntryList = iterator;
                } else {
                    iterator->TState = ObjectWaiter::TS_CXQ;
                    for (;;) {
                        ObjectWaiter *Front = _cxq;
                        iterator->_next = Front;
                        if (Atomic::cmpxchg_ptr(iterator, &_cxq, Front) == Front) {
                            break;
                        }
                    }
                }
            } else if (Policy == 3) {      // 尾插 cxq
                iterator->TState = ObjectWaiter::TS_CXQ;
                for (;;) {
                    ObjectWaiter *Tail;
                    Tail = _cxq;
                    if (Tail == NULL) {
                        iterator->_next = NULL;
                        if (Atomic::cmpxchg_ptr(iterator, &_cxq, NULL) == NULL) {
                            break;
                        }
                    } else {
                        while (Tail->_next != NULL) Tail = Tail->_next;
                        Tail->_next = iterator;
                        iterator->_prev = Tail;
                        iterator->_next = NULL;
                        break;
                    }
                }
            } else {
                ParkEvent *ev = iterator->_event;
                iterator->TState = ObjectWaiter::TS_RUN;
                OrderAccess::fence();
                ev->unpark();
            }
    
            if (Policy < 4) {
                iterator->wait_reenter_begin(this);
            }
        }
        // 自旋释放
        Thread::SpinRelease(&_WaitSetLock);
    
        if (iterator != NULL && ObjectMonitor::_sync_Notifications != NULL) {
            ObjectMonitor::_sync_Notifications->inc();
        }
    }
    

    总结

    本文主要介绍了synchronized关键字的使用方式,锁升级过程,以及相关源码分析,源码这块主要介绍了同步队列和等待队列之间的转移过程,同步队列包括_cxq队列和_entryList队列,抢锁失败的线程会先放到_cxq队列的头部,在拥有锁的线程释放锁时,会根据不同的QMode,选择从_cxq直接取值,或者将_cxq头插或尾插到_entryList队列中,具体可以见队列转换图。如果直接从_cxq取值的话,就会是后阻塞的线程先唤醒。
    从整个过程来看,synchronized整体加锁的思路和对队列的处理与aqs都比较类似,下面贴下两者的相同与区别:


    image.png

    参考链接

    synchronized 关键字详解
    并发编程的艺术
    不可不说的Java“锁”事

    相关文章

      网友评论

          本文标题:Sychronized关键字使用及原理解析

          本文链接:https://www.haomeiwen.com/subject/vnslprtx.html