精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

面試官再問 synchronized 底層原理,這樣回答讓他眼前一亮!

開發(fā)
本文針對synchronized關(guān)鍵字上鎖、釋放鎖、等待通知等操作的底層機制和優(yōu)化理念進行了深入分析和講解,希望對你有所啟發(fā)。

因為之前關(guān)于synchronized關(guān)鍵字底層工作機制理解有所偏差,簡單查閱了一下其底層的實現(xiàn),遂以此文簡單記錄一下jdk8版本synchronized關(guān)鍵字的設(shè)計理念和實現(xiàn),希望對你有所幫助。

一、基于objectMonitor深入剖析synchronized關(guān)鍵字

1. 宏觀了解synchronized同步機制

在正式分析synchronized關(guān)鍵字之前,我先從全局的維度了解一下synchronized關(guān)鍵字的實現(xiàn)。假設(shè)我們現(xiàn)在有三個線程嘗試爭搶對象鎖,此時線程0最先通過CAS機制將鎖的持有者標識為自己并成功獲取鎖,對應的線程1和線程2則CAS失敗,此階段線程0就可以操作鎖內(nèi)部對應的臨界資源。同理,這個持有者標識就是_owner字段:

線程1和線程2在競爭失敗后,會先嘗試自旋獲取鎖,如果自旋失敗則會進入阻塞隊列中等待,直到鎖釋放,而這個隊列可以是_cxq或者_EntryList。這里筆者為了簡單直觀地展現(xiàn)這一過程,將競爭失敗的線程直接放入_EntryList隊列中:

線程0調(diào)用同一個鎖對應的示例的同一個方法,例如我們之前上鎖調(diào)用的是function1現(xiàn)在調(diào)用的是function2,這也就是我們常說的同一個對象嘗試上鎖兩次也就是鎖重入的情況,對應底層的objectMonitor會針對這種情況進行維護,也就是通過一個_recursions字段進行累加,這也就意味著線程0進行鎖釋放時必須釋放兩次:

public class Example1 {

    public synchronized void function1(){
        Console.log("function1");
          
    }

    public synchronized void function2(){
        Console.log("function2");
      
    }

   
}

線程0期間因為特定原因,主動掛起調(diào)用wait方法,此時就需要完成鎖釋放操作,對應底層操作則是將其存入等待隊列_WaitSet中,并將_EntryList中等待的線程喚醒嘗試競爭鎖(注意,不一定完全是_EntryList,這里更多是為了舉例做一個普適的說明)。

后續(xù)線程0被notify或者notifyall喚醒之后,還會再次回到_EntryList競爭鎖。

自此我們從宏觀的角度了解了synchronized底層的工作機制,對應的我們也通過源碼的角度給出了上述概念中的幾個成員字段,整體和上述表述類似

  • _count:等待獲取該鎖的線程數(shù),通常為_cxq和_EntryList隊列中的線程數(shù)總和
  • _waiters:調(diào)用wait方法進入等待狀態(tài)的線程數(shù)
  • _owner:指向當前持有鎖的線程
  • _recursions:線程重入次數(shù),用于記錄當前線程對同一把鎖的重入次數(shù)
ObjectMonitor() {
    _header       = NULL;
    _count        = 0;//搶占該鎖的線程數(shù)即 _cxq.size + EntryList.size
    _waiters      = 0,//處于等待狀態(tài)的線程數(shù)
    _recursions   = 0;//線程重入次數(shù)
    _object       = NULL;
    _owner        = NULL;//當前鎖即ObjectMonitor的擁有者
    _WaitSet      = NULL;//處于wait狀態(tài)的線程會存入到該隊列中
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;//多線程競爭時會進入該隊列中
    FreeNext      = NULL ;
    _EntryList    = NULL ;//處于block等待鎖狀態(tài)的線程會進入該隊列
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

上述說明我們也可以在源碼objectMonitor.cpp中的注釋中查閱到,感興趣的讀者可以翻閱這段注釋了解具體的實現(xiàn)細節(jié),其大體意思與上述描述基本一致。對應的,筆者簡單概括如下:

  • 線程必須通過cas將owner從null變?yōu)樽约翰潘愠晒Λ@得鎖
  • 任意時刻線程因阻塞等待必須處于cxq或EntryList隊列中,或者因wait調(diào)用進入WaitSet隊列
  • 持有鎖線程釋放后,其必須喚醒EntryList一個候選線程爭搶鎖
  • ......

2. 并發(fā)競爭監(jiān)視鎖

線程基于synchronized關(guān)鍵字爭搶鎖資源的本質(zhì)實現(xiàn)就是objectMonitor.cpp的enter方法,其上鎖步驟嚴格:

(1) 嘗試cas將null設(shè)置為當前線程的指針,如果cas方法返回null則說明自己獲取鎖成功直接返回,反之進入步驟2

(2) 如果非空但返回指針地址是自己,說明當前線程之前已經(jīng)獲取過一個這把鎖,本次為鎖重入,直接累加重入次數(shù)_recursions后返回,反之進入步驟3

(3) 判斷當前線程是否是這把鎖的輕量級鎖持有者,如果是則執(zhí)行一次鎖升級將其升級為重量級鎖,并將owner對象中棧上BasicLockObject地址轉(zhuǎn)為完整的thread指針,反之進入步驟4

(4) 上述步驟都不成功,則說明當前鎖被其他線程持有,嘗試自旋幾次獲取這把鎖如果成功則直接返回,反之進入步驟5

(5) 步驟4還是沒有拿到鎖,為了避免非必要的CPU自旋開銷,累加count說明等待獲取鎖的線程數(shù)加一后,再次自旋嘗試幾次,

(6) 幾次無果將當前線程封裝為等待節(jié)點存入等待隊列,直到被喚醒拿到鎖后退出

(7) 通過步驟6的方式拿到鎖的線程,會通過cas的方式完成等待線程數(shù)count值扣減并退出

對應的我們給出了這段代碼的流程圖,讀者可參考上述說明理解:

有了上述的基礎(chǔ)之后,我們就可以查閱objectMonitor.cpp上鎖邏輯enter的實現(xiàn),邏輯和上述說明基本一致,我們也不難看出對應jdk8版本的synchronized關(guān)鍵字內(nèi)置的優(yōu)化,即核心理念就是盡可能利用cas競爭鎖資源,明確感知到競爭激烈之后主動將其掛起,再利用等待通知模型喚醒線程競爭資源:

void ATTR ObjectMonitor::enter(TRAPS) {
//獲取到當前線程的指針
  Thread * const Self = THREAD ;
void * cur ;
//嘗試CAS的方式將owner從null設(shè)置為當前線程
  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
//如果返回null,說明當前線程是第一個獲取到這把鎖的,直接返回
if (cur == NULL) {
    //......
     return ;
  }
//非null且指針就是自己說明是重入,直接累加重入次數(shù)_recursions后返回
if (cur == Self) {
     //......
     _recursions ++ ;
     return ;
  }
//如果當前線程是輕量級鎖的持有則,則進入該邏輯進行鎖升級
if (Self->is_lock_owned ((address)cur)) {
    //......
    //將owner從線程棧上的BasicLockObject地址轉(zhuǎn)換為完整的Thread指針
    _owner = Self ;
    OwnerIsThread = 1 ;
    return ;
  }

// We've encountered genuine contention.
//當前鎖被其他線程占有,需要搶占
  assert (Self->_Stalled == 0, "invariant") ;
//記錄需要搶占的線程monitor指針
  Self->_Stalled = intptr_t(this) ;


//嘗試自旋獲取鎖,成功后返回
if (Knob_SpinEarly && TrySpin (Self) > 0) {
     //......
     return ;
  }
//......
//經(jīng)過上述步驟還是沒有搶到鎖,原子累加count值,即增加一個等待獲取鎖的線程
  Atomic::inc_ptr(&_count);

  EventJavaMonitorEnter event;

//......
    for (;;) {
      jt->set_suspend_equivalent();
      // cleared by handle_special_suspend_equivalent_condition()
      // or java_suspend_self()
      //嘗試CAS或者自旋等方式獲取鎖,若失敗則通過頭插法將線程存入cxq隊列中,等待后續(xù)通知喚醒
      EnterI (THREAD) ;

      if (!ExitSuspendEquivalent(jt)) break ;

      //......
      //完成操作后,鎖釋放通知其他線程上鎖
      exit (false, Self) ;

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

   
    // acquire it.
  }
//原子扣減count告知等待鎖的線程少一個
  Atomic::dec_ptr(&_count);
//......
}

3. 鎖釋放流程解析

對于鎖釋放流程,整體邏輯比較簡單,對應的執(zhí)行步驟為:

(1) 查看當前線程是否是owner指針指向的線程,如果明確當前監(jiān)視鎖非輕量級鎖,則說明當前線程并非監(jiān)視鎖持有者(進入monitor但沒有正確退出),則直接返回,反之進入步驟2

(2) 查看_recursions是否不為0,若不為0則說明是重入,扣減_recursions后返回

(3) 上述檢查無誤,將鎖直接釋放,然后進入步驟4

(4) 當前線程進行cas嘗試將owner從null設(shè)置為自己以查看是否存在其他線程競爭,如果失敗則說明鎖被其他線程持有,不需要執(zhí)行后續(xù)喚醒線程的任務直接返回,反之進入步驟5

(5) 這也就意味著當前監(jiān)視鎖沒有被其它線程獲取,當前線程需要執(zhí)行喚醒等待線程的任務

(6) 到這一步就意味著需要喚醒等待線程的步驟了,筆者上文說過,喚醒線程不一定是從EntryList也可能從cxq隊列,對應的喚醒策略由QMode決定,具體規(guī)則如下:

1. QMode為2直接喚醒cxq隊列首元素線程,讓其競爭監(jiān)視鎖
2. QMode為3,則查看cxq隊列是否非空,若明確非空則將其追加到EntryList中,若EntryList為空則直接將cxq隊列設(shè)置為EntryList,然后將首元素喚醒
3. QMode為4,則將EntryList追加到cxq隊列后,然后讓cxq隊列作為EntryList首元素,將首個線程喚醒競爭監(jiān)視鎖


/************************* 執(zhí)行到步驟4和5,說明代碼中的邏輯判斷明確EntryList為空,對應不同的QMode規(guī)則為**************/


4. QMode為1,將cxq隊列倒敘排列后作為EntryList,并將首元素喚醒
5. QMode為0或2,將cxq直接封裝為ObjectWaiter作為EntryList,并將首元素喚醒

(7) 特殊步驟:如果發(fā)現(xiàn)w為空則說明cxq和EntryList隊列都為空,則重新從循環(huán)頭開始執(zhí)行正確的退出協(xié)議

對應如下是筆者整理的流程圖,讀者可以基于上述說明進行梳理歸納:

對應位于objectMonitor.cpp的exit函數(shù)就是我們釋放鎖的邏輯,這里筆者整理出核心的代碼段,讀者可結(jié)合注釋更進一步理解:

void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
   Thread * Self = THREAD ;
   //如果當前線程不等于_owner則進入當前邏輯
   if (THREAD != _owner) {
     if (THREAD->is_lock_owned((address) _owner)) {//如果當前線程持有這把鎖的輕量級鎖,則將其設(shè)置為重量級鎖并讓_owner指向完整的線程地址,然后執(zhí)行后續(xù)的鎖釋放邏輯
      //......
       assert (_recursions == 0, "invariant") ;
       _owner = THREAD ;
       _recursions = 0 ;
       OwnerIsThread = 1 ;
     } else {
     //......
       /*
     
       JNI(Java Native Interface)本地代碼中可能出現(xiàn)的監(jiān)視器(即 synchronized 鎖)使用不平衡的情況,
       比如進入 monitor 但沒有正確退出,或者退出沒有對應的進入。當檢測到這種異常情況時,應該拋出 Java 的
       IllegalMonitorStateException 異常來通知開發(fā)者。
       */
       TEVENT (Exit - Throw IMSX) ;
       assert(false, "Non-balanced monitor enter/exit!");
       if (false) {
          THROW(vmSymbols::java_lang_IllegalMonitorStateException());
       }
       return;
     }
   }

   //_recursions不為0,說明此前線程重入過,完成_recursions扣減后返回,不重置owner指針
   if (_recursions != 0) {
     _recursions--;        // this is simple recursive enter
     TEVENT (Inflated exit - recursive) ;
     return ;
   }

   //......

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


      if (Knob_ExitPolicy == 0) {
        //......

         //將ownder設(shè)置為null,即釋放鎖
         OrderAccess::release_store_ptr (&_owner, NULL) ;   // drop the lock
         OrderAccess::storeload() ;                         // See if we need to wake a successor
        //......
         //cas嘗試一下線程從null設(shè)置為自己,如果非空則說明鎖被其他線程持有,不執(zhí)行后續(xù)喚醒其他線程的邏輯
         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
           //......
            //嘗試cas上鎖,如果不成功則說明當前鎖被其他線程持有,則當前線程不負責后續(xù)喚醒等待線程的職責,直接返回
            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) ;
         }
      }

    //......

      ObjectWaiter * w = NULL ;
      int QMode = Knob_QMode ;

      if (QMode == 2 && _cxq != NULL) {//QMode為2且cxq隊列非空,直接喚醒cxq隊列首元素直接返回
        
          w = _cxq ;
          assert (w != NULL, "invariant") ;
          assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
          //傳入_cxq隊列首元素地址,喚醒對應線程去競爭鎖資源
          ExitEpilog (Self, w) ;
          return ;
      }

      if (QMode == 3 && _cxq != NULL) {
       
        //
          // 通過CAS將_cxq設(shè)置為空
          // 將w(即原有的cxq隊列)轉(zhuǎn)換為雙向鏈表的waiter
          // 將最近到達的線程(RATs)追加到EntryList
          // TODO:將EntryList組織為循環(huán)雙向鏈表(CDLL),這樣我們就能在常量時間內(nèi)定位到尾部。
          // 把cxq掛到_EntryList隊列上,后續(xù)從_EntryList中喚醒
          // 查找EntryList的尾部
          // 如果EntryList為空,則將w設(shè)為EntryList
          // 否則將w鏈接到EntryList尾部
          w = _cxq ;
          for (;;) {
             assert (w != NULL, "Invariant") ;
             //通過cas把cxq設(shè)置為空
             ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
             if (u == w) break ;
             w = u ;
          }
      

          ObjectWaiter * q = NULL ;
          ObjectWaiter * p ;
          //將w即原有cxq隊列轉(zhuǎn)為一個雙向鏈表的waiter
          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 ;
          }

     
          //如果_EntryList非空的話直接將其追加到cxq隊列,反之cxq隊列直接作為_EntryList
          ObjectWaiter * Tail ;
          for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
          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) {//如果QMode且_cxq非空,則把_EntryList掛到cxq上
          // Aggressively drain cxq into EntryList at the first opportunity.
     
          //w指針指向cxq隊列后,通過cas將cxq指針置空
          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") ;
          //將w(原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 ;
          }

          // Prepend the RATs to the EntryList
          //將_EntryList追加到w(原cxq隊列)
          if (_EntryList != NULL) {
              q->_next = _EntryList ;
              _EntryList->_prev = q ;
          }
          _EntryList = w ;

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

      w = _EntryList  ;
      if (w != NULL) {//從_EntryList找到節(jié)點喚醒指定線程返回
      
          assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }

   
      w = _cxq ;
      //如果發(fā)現(xiàn)w為空則說明cxq和entrylist隊列都為空,則重新從循環(huán)頭開始執(zhí)行正確的退出協(xié)議
      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 : drain cxq to EntryList, reversing order
         // We also reverse the order of the list.
         //當 QMode 設(shè)置為 1 時,
         //ObjectMonitor 會將 cxq(競爭隊列)中的所有等待線程移動到 EntryList(入口列表)中,
         // 但在這個過程中會將線程的順序顛倒。這種設(shè)計可能是為了實現(xiàn)某種調(diào)度策略,比如讓最后到達的線程優(yōu)先獲得鎖(LIFO - 后進先出)
         ObjectWaiter * s = NULL ;
         ObjectWaiter * t = w ;
         ObjectWaiter * u = NULL ;
         while (t != NULL) { //將cxq隊列元素倒敘排列一下
             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 直接順序生成等待節(jié)點
         _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 ;
         }
      }

   
      //拿到首元素喚醒,讓其嘗試競爭監(jiān)視鎖資源
      w = _EntryList  ;
      if (w != NULL) {
          guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
          ExitEpilog (Self, w) ;
          return ;
      }
   }

3. 掛起等待的wait調(diào)用

線程調(diào)用wait方法時就會釋放鎖進入等待隊列,等待超時或者被喚醒后重新競爭監(jiān)視鎖資源,對應的處理步驟為:

(1) 進行必要的中斷檢查,如果發(fā)現(xiàn)調(diào)用wait時線程正在處理中斷,則直接拋出中斷異常,反之進入步驟2

(2) 將節(jié)點封裝為等待節(jié)點

(3) 自旋獲取等待隊列的鎖,將線程存入等待隊列

(4) 保存好_recursions重入次數(shù)后,調(diào)用exit退出監(jiān)視鎖

(5) 按照wait傳參進行有限或者無限時間的等待,直到被喚醒

(6) 喚醒后,查看自己當前狀態(tài)如果處于TS_RUN則直接嘗試競爭鎖,如果是TS_ENTER或者TS_CXQ則進行必要的自旋嘗試競爭鎖后掛起等待

(7) 上鎖成功后,恢復鎖重入次數(shù),扣減_waiters告知等待線程減少一個

(8) 操作臨界資源

這段邏輯比較簡單,讀者可直接基于上述說明查看wait代碼的底層實現(xiàn):

void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
//Self指向當前線程的指針
   Thread * const Self = THREAD ;
   //......

   // check for a pending interrupt
   // 檢查是否存在待處理的中斷,若明確處理中斷則直接拋出中斷異常
   if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) {
     //......
     TEVENT (Wait - Throw IEX) ;
     THROW(vmSymbols::java_lang_InterruptedException());
     return ;
   }

//......
   //將當前節(jié)點封裝為wait
   ObjectWaiter node(Self);
   node.TState = ObjectWaiter::TS_WAIT ;
   Self->_ParkEvent->reset() ;
   //......
   //自旋獲取waitSet鎖將節(jié)點存入waiterSet然后釋放鎖
   Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ;
   AddWaiter (&node) ;
   Thread::SpinRelease (&_WaitSetLock) ;


   intptr_t save = _recursions; // record the old recursion count 保存舊的遞歸計數(shù),便于后續(xù)喚醒恢復
   _waiters++;                  // increment the number of waiters 增加等待者數(shù)量
   _recursions = 0;             // set the recursion level to be 1 將遞歸級別設(shè)置為1
   
   exit (true, Self) ;                    // exit the monitor 退出監(jiān)視器


   //......

   int ret = OS_OK ;
   int WasNotified = 0 ;
   //獲取當前線程的park掛起
   { // State transition wrappers
     OSThread* osthread = Self->osthread();
     OSThreadWaitState osts(osthread, true);
     {
      //......

       if (interruptible && (Thread::is_interrupted(THREAD, false) || HAS_PENDING_EXCEPTION)) {
           // Intentionally empty
           // 故意為空
       } else
       //按照指定的傳入等待時間參數(shù)等待,若沒設(shè)置時間則無限期掛起等待直到喚醒
       if (node._notified == 0) {
         if (millis <= 0) {
            Self->_ParkEvent->park () ;
         } else {
            ret = Self->_ParkEvent->park (millis) ;
         }
       }

      //......

     } // Exit thread safepoint: transition _thread_blocked -> _thread_in_vm
     //......


   

     //自旋獲取鎖將線程中等待節(jié)點移除,并將其狀態(tài)設(shè)置為TS_RUN,嘗試爭搶鎖
     if (node.TState == ObjectWaiter::TS_WAIT) {
         Thread::SpinAcquire (&_WaitSetLock, "WaitSet - unlink") ;
         if (node.TState == ObjectWaiter::TS_WAIT) {
            DequeueSpecificWaiter (&node) ;       // unlink from WaitSet
            // 從WaitSet中解除鏈接
            assert(node._notified == 0, "invariant");
            node.TState = ObjectWaiter::TS_RUN ;
         }
         Thread::SpinRelease (&_WaitSetLock) ;
     }

   

     //......
     ObjectWaiter::TStates v = node.TState ;
     //查看被喚醒的線程當前處于什么狀態(tài),如果狀態(tài)為TS_RUN則調(diào)用enter直接去競爭鎖,反之說明節(jié)點在等待隊列中,進行必要的自旋競爭后進入cxq或者entrylist中等待喚醒
     if (v == ObjectWaiter::TS_RUN) {
         enter (Self) ;
     } else {
         guarantee (v == ObjectWaiter::TS_ENTER || v == ObjectWaiter::TS_CXQ, "invariant") ;
         ReenterI (Self, &node) ;
         node.wait_reenter_end(this);
     }

     //......
   } 
//......
   _recursions = save;     // restore the old recursion count 恢復舊的遞歸計數(shù)

   _waiters--;             // decrement the number of waiters 減少等待者數(shù)量



//......


}

4. 通知喚醒的notify操作

在分析wait源碼的時候,筆者提及wait喚醒的隊列可能在cxq或者在entryList隊列中,本質(zhì)上是notify操作的等待隊列處理策略,當線程通過notify操作嘗試通知處于waitset中的線程時,其底層的notify調(diào)用整體執(zhí)行步驟為:

(1) 判斷_WaitSet是否非空,若非空執(zhí)行步驟2

(2) 通過自旋獲取_WaitSet的鎖,著手處理_WaitSet中的線程

(3) 上鎖成功后,按照配置的策略處理_WaitSet中的元素,具體策略為:

策略0:如果EntryList非空的話,則將等待者添加到EntryList的頭部
策略1:將等待者添加到EntryList的尾部
策略2:將等待者添加到cxq的頭部
策略3:將等待者添加到cxq的尾部
其他:  直接喚醒WaitSet中的首個線程,讓其競爭監(jiān)視鎖

(4) 釋放WaitSet鎖

notify源碼邏輯比較清晰簡單,讀者可結(jié)合筆者的注釋自行閱讀了解一下細節(jié),并串聯(lián)上述的說明完成邏輯閉環(huán):

void ObjectMonitor::notify(TRAPS) {
  CHECK_OWNER();

//_WaitSet為空直接返回
if (_WaitSet == NULL) {
     TEVENT (Empty-Notify) ;
     // 記錄空通知事件
     return ;
  }
  DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);

int Policy = Knob_MoveNotifyee ;

//通過自旋獲取等待隊列的鎖
  Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notify") ;
//拿到waitSet
  ObjectWaiter * iterator = DequeueWaiter() ;
// 從等待隊列中取出一個等待者
if (iterator != NULL) {
     TEVENT (Notify1 - Transfer) ;
     guarantee (iterator->TState == ObjectWaiter::TS_WAIT, "invariant") ;
     guarantee (iterator->_notified == 0, "invariant") ;
     if (Policy != 4) {
        iterator->TState = ObjectWaiter::TS_ENTER ;
        // 將等待者狀態(tài)設(shè)置為進入狀態(tài)
     }
     iterator->_notified = 1 ;

     Thread * Self = THREAD;
     iterator->_notifier_tid = Self->osthread()->thread_id();
   
     //定位到等待隊列
     ObjectWaiter * List = _EntryList ;
     // 獲取EntryList
     if (List != NULL) {
        assert (List->_prev == NULL, "invariant") ;
        assert (List->TState == ObjectWaiter::TS_ENTER, "invariant") ;
        assert (List != iterator, "invariant") ;
     }
     // 策略0:如果EntryList非空的話,則將等待者添加到EntryList的頭部
     if (Policy == 0) {       // prepend to EntryList
         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) {      // append to EntryList
         // 策略1:將等待者添加到EntryList的尾部
         if (List == NULL) {
             iterator->_next = iterator->_prev = NULL ;
             _EntryList = iterator ;
             // 如果EntryList為空,則直接將等待者設(shè)為EntryList
         } else {
       
            ObjectWaiter * Tail ;
            for (Tail = List ; Tail->_next != NULL ; Tail = Tail->_next) ;
            // 查找EntryList的尾部
            assert (Tail != NULL && Tail->_next == NULL, "invariant") ;
            // 斷言:尾部不為NULL且下一個元素為NULL
            Tail->_next = iterator ;
            iterator->_prev = Tail ;
            iterator->_next = NULL ;
            // 將等待者添加到EntryList的尾部
        }
     } else
     //策略2:將等待者添加到cxq的頭部
     if (Policy == 2) {      // prepend to cxq
       
         // prepend to cxq
         if (List == NULL) {
             iterator->_next = iterator->_prev = NULL ;
             _EntryList = iterator ;
             // 如果EntryList為空,則直接將等待者設(shè)為EntryList
         } else {
            iterator->TState = ObjectWaiter::TS_CXQ ;
            // 將等待者狀態(tài)設(shè)置為CXQ狀態(tài)
            for (;;) {
                ObjectWaiter * Front = _cxq ;
                iterator->_next = Front ;
                if (Atomic::cmpxchg_ptr (iterator, &_cxq, Front) == Front) {
                    break ;
                    // 原子性地將等待者添加到cxq的頭部
                }
            }
         }
     } else
     //策略3:將等待者添加到cxq的尾部
     if (Policy == 3) {      // append to cxq
      
        iterator->TState = ObjectWaiter::TS_CXQ ;
        // 將等待者狀態(tài)設(shè)置為CXQ狀態(tài)
        for (;;) {
            ObjectWaiter * Tail ;
            Tail = _cxq ;
            if (Tail == NULL) {
                iterator->_next = NULL ;
                if (Atomic::cmpxchg_ptr (iterator, &_cxq, NULL) == NULL) {
                   break ;
                   // 如果cxq為空,則原子性地將等待者設(shè)為cxq
                }
            } else {
                while (Tail->_next != NULL) Tail = Tail->_next ;
                // 查找cxq的尾部
                Tail->_next = iterator ;
                iterator->_prev = Tail ;
                iterator->_next = NULL ;
                break ;
                // 將等待者添加到cxq的尾部
            }
        }
     } else {
        ParkEvent * ev = iterator->_event ;
        // 獲取等待者的事件
        iterator->TState = ObjectWaiter::TS_RUN ;
        // 將等待者狀態(tài)設(shè)置為運行狀態(tài)
        OrderAccess::fence() ;
        // 內(nèi)存屏障
        ev->unpark() ;
        // 直接喚醒等待者
     }

   
  }
// 釋放WaitSet鎖
  Thread::SpinRelease (&_WaitSetLock) ;


//......
}

二、關(guān)于synchronized更進一步的理解

1. 為什么鎖釋放之后還要進行一次cas上鎖

上文提到執(zhí)行exit方法時,線程通過release_store_ptr完成鎖釋放之后,會嘗試cas再次嘗試將鎖的owner指向自己,這樣做的目的是什么呢?

//釋放鎖
OrderAccess::release_store_ptr (&_owner, NULL) ;   // drop the lock
//......
//cas上鎖
if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
            return ;
}
//執(zhí)行后續(xù)喚醒邏輯

這本質(zhì)上是針對cpu時間片的利用和調(diào)度效率上的一種優(yōu)化,即當前線程完成鎖釋放后,盡可能利用當前線程還在使用時間片的時刻,嘗試cas上鎖,如果上鎖成功則說明當前鎖沒有人獲取,則執(zhí)行后續(xù)喚醒等待線程的邏輯,由此盡可能利用cpu時間片避免擱淺問題(鎖釋放了所有線程還是處于park狀態(tài)):

2. 自旋重試的性能壓榨

競爭鎖資源的enter方法在明確幾次cas和自旋失敗后,會將其添加到cxq隊列中,但設(shè)計者并不會直接添加,而是盡可能利用每一次機會,利用我們將線程封裝為等待節(jié)點會通過cas存入等待隊列,為了盡可能利用每一次cpu時間片,再進行cas自旋入隊時,設(shè)計者會利用每次cas失敗的時機再次嘗試自旋上鎖,這本質(zhì)就是對于入隊激烈競爭的一種臨時規(guī)避,盡可能利用這個間隙再次獲取鎖資源:

for (;;) {
      //當前節(jié)點指向cxq隊列的首元素
        node._next = nxt = _cxq ;
        //通過cas將當前元素設(shè)置為cxq隊列的首元素,若成功則退出循環(huán)
        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.
        //若cas失敗則再次嘗試自旋上鎖,盡可能利用cpu執(zhí)行開銷
        if (TryLock (Self) > 0) {
            assert (_succ != Self         , "invariant") ;
            assert (_owner == Self        , "invariant") ;
            assert (_Responsible != Self  , "invariant") ;
            return ;
        }
    }

3. 不同喚醒策略的設(shè)計理念

鎖釋放時,源碼中對于線程的喚醒策略給出了大體5種策略,實際上這也是對系統(tǒng)資源規(guī)律的把握和不同場景效率的優(yōu)化,對應的筆者結(jié)合之前整理的幾種策略進行說明:

1. QMode為2直接喚醒cxq隊列首元素線程,讓其競爭監(jiān)視鎖,這種方式避免cxq轉(zhuǎn)移到EntryList的開銷,直接讓cxq隊列元素喚醒,適用于追求高并發(fā)和極致的場景,不需要考慮公平性
2. QMode為3,則查看cxq隊列是否非空,若明確非空則將其追加到EntryList尾部,若EntryList為空則直接將cxq隊列設(shè)置為EntryList,然后將首元素喚醒,因為線程競爭不到鎖資源之后是采用頭插法存入cxq隊列,所以為了避免cxq反序追加到EntryList這個開銷,該策略是直接將cxq隊列追加到EntryList上,這本質(zhì)上就是一種公平性和性能的折中
3. QMode為4,則將EntryList追加到cxq隊列后,然后讓cxq隊列作為EntryList首元素,將首個線程喚醒競爭監(jiān)視鎖,這種方案是考慮到最新的線程會位于cxq隊列隊首,所以cpu緩存中可能存有這個線程的緩存數(shù)據(jù),因此優(yōu)先喚醒該線程保證高效完成計算
/************************* 執(zhí)行到步驟4和5,說明代碼中的邏輯判斷明確EntryList為空,對應不同的QMode規(guī)則為**************/
4. QMode為1,將cxq隊列倒序排列后作為EntryList,并將首元素喚醒,這種就是將頭插法的cxq隊列順序排列追加到EntryList,嚴格保證調(diào)度的公平性,避免線程饑餓
5. QMode為0(默認策略),將cxq直接封裝為ObjectWaiter作為EntryList,繞過兩個列表關(guān)聯(lián)直接將首元素喚醒,適用于高并發(fā)和高性能的場景

4. 為什么wait獲取鎖采用自旋而非重量級鎖

_WaitSetLock保護的等待隊列本質(zhì)上基本只有監(jiān)視器所有者進行訪問,個別情況由于超時中斷返回操作隊列的情況,所以競爭不算激烈,所以在操作等待隊列時上鎖的方式是采用自旋而非重量級鎖:

Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ;
   AddWaiter (&node) ;
   Thread::SpinRelease (&_WaitSetLock) ;

責任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關(guān)推薦

2023-08-10 08:16:41

Hash技術(shù)哈希表

2017-03-06 18:35:22

VRAR應用

2021-08-17 11:20:25

Vue前端技巧

2022-12-19 08:23:24

2023-07-16 22:37:46

JavaScript代碼任務

2024-06-17 10:24:21

2025-11-14 04:00:00

2022-07-28 15:46:08

Linux工具

2021-06-30 09:56:24

MySQL數(shù)據(jù)庫索引

2020-12-08 13:09:02

面試官質(zhì)數(shù)枚舉

2018-01-29 19:33:11

程序員項目開發(fā)

2020-10-23 07:43:31

String

2024-03-14 17:41:25

AIGC人工智能應用

2022-02-28 23:37:16

iOS蘋果系統(tǒng)

2024-04-11 08:33:25

2020-04-20 13:11:21

HashMap底層存儲

2024-08-29 16:30:27

2024-02-29 16:49:20

volatileJava并發(fā)編程
點贊
收藏

51CTO技術(shù)棧公眾號

爱情电影网av一区二区 | 疯狂蹂躏欧美一区二区精品| 成人欧美一区二区三区视频xxx | 香蕉视频网站在线观看| 韩国成人福利片在线播放| 色中色综合影院手机版在线观看| 亚洲激情 欧美| 欧美成人app| 亚洲一本大道在线| 日韩亚洲一区在线播放| www.av网站| 日韩精品五月天| 欧美大片在线看| 91精品人妻一区二区三区| 日韩在线网址| 欧美色国产精品| www.成年人视频| 3p在线观看| 99re热视频这里只精品| 亚洲在线观看视频| 高潮毛片又色又爽免费| 欧美三级乱码| 色偷偷888欧美精品久久久| 添女人荫蒂视频| 国语精品视频| 欧美日韩一区精品| 久久精品.com| 91九色美女在线视频| 日韩码欧中文字| 欧美高清性xxxxhdvideosex| 亚洲国产日韩在线观看| 精品在线观看免费| 国产成人一区三区| 91精品国产乱码久久久张津瑜| 亚洲一级淫片| 日韩最新免费不卡| 国产美女永久免费无遮挡| 青青视频一区二区| 亚洲高清一二三区| 日本黄色三级网站| 精品视频在线观看网站| 欧美日韩不卡一区二区| 国模杨依粉嫩蝴蝶150p| 这里有精品可以观看| 亚洲精品成人a在线观看| 一区二区不卡在线| 亚洲精品承认| 国产精品国模大尺度视频| 少妇精品久久久久久久久久| 男人天堂资源在线| 久久久精品日韩欧美| 精品网站在线看| 五月激情婷婷综合| 91麻豆国产精品久久| 极品尤物一区二区三区| 天天操天天干天天爽| fc2成人免费人成在线观看播放 | 久久久在线观看| 国产午夜精品无码一区二区| 极品av少妇一区二区| 久久久噜噜噜久久久| 精品无码人妻一区二区三| 亚洲图片在线| 97久久精品国产| 日日摸天天添天天添破| 日日摸夜夜添夜夜添精品视频 | 久久久久成人黄色影片| 日韩三级电影| 动漫一区在线| 亚洲国产成人av网| 人妻熟女一二三区夜夜爱| 欧美大片高清| 欧美日韩成人一区二区| 爱情岛论坛亚洲自拍| 一区二区三区四区精品视频 | 国产一级在线观看| 国产精品无码永久免费888| 在线免费观看一区二区三区| 黄色在线播放网站| 午夜一区二区三区在线观看| 97在线播放视频| 欧美少妇激情| 精品国产亚洲在线| 国产高清一区二区三区四区| 小小影院久久| 456国产精品| 在线观看国产黄| 粉嫩av一区二区三区在线播放| 精品国产乱码久久久久久丨区2区| 黄色av网站在线看| 亚洲精品中文在线影院| 北条麻妃在线视频观看| 成人精品三级| 亚洲丁香久久久| 538精品视频| 欧美精品aa| 国产精品电影观看| 蜜桃在线一区二区| 国产精品女主播在线观看| 欧美日韩中文字幕在线播放| 毛片无码国产| 欧美大片顶级少妇| 人妻无码一区二区三区免费| 在线综合亚洲| 97超级在线观看免费高清完整版电视剧| 天天躁日日躁狠狠躁喷水| 中文字幕中文在线不卡住| 欧美日韩性生活片| 国产精品一区二区三区av | 91欧美在线视频| 亚洲成人免费在线观看| 日本中文字幕影院| 免费精品国产| 国内精品免费午夜毛片| 91九色蝌蚪91por成人| 久久人人爽爽爽人久久久| 91精品国产毛片武则天| 久久久久久久性潮| 亚洲欧美另类在线观看| 国产 日韩 欧美 成人| 极品少妇一区二区三区精品视频| 九九九九精品| 久久av色综合| 欧美一区二区三区电影| 女人裸体性做爰全过| 老司机午夜精品视频在线观看| 超碰97在线播放| 蜜芽在线免费观看| 欧美午夜精品免费| 国产美女免费无遮挡| 中文精品在线| 国产精品国产精品| 性欧美高清come| 制服视频三区第一页精品| 亚洲欧美va天堂人熟伦| 欧美专区18| 欧美日韩在线一区二区三区| 女厕盗摄一区二区三区| 欧美精品一区二区三区一线天视频| 永久久久久久久| 国产一区二区三区黄视频| 一区二区三区四区国产| 成人免费在线观看视频| 在线性视频日韩欧美| 中文在线观看av| 日本一区二区视频在线| 欧美精品成人网| 精品免费视频| 国产精品网址在线| 日韩子在线观看| 欧美美女一区二区在线观看| 一本在线免费视频| 精品一区二区在线看| 中文字幕一区二区三区四区五区六区 | 黄色片视频在线| 手机在线电影一区| 成人欧美一区二区三区在线 | 午夜精品在线视频| 污污视频在线观看网站| 黑人巨大精品欧美一区二区免费| 国产视频久久久久久| 国产亚洲在线观看| 日本视频一区在线观看| 成人精品久久一区二区三区| 青青草在线免费观看| 日韩欧美在线视频| 摸摸摸bbb毛毛毛片| 日本vs亚洲vs韩国一区三区二区| 四虎影院一区二区三区 | 韩国av网站在线| 欧美一二三四区在线| 久久机热这里只有精品| av日韩在线网站| 日韩中文字幕二区| 天天色天天射综合网| 懂色av一区二区三区在线播放| 久草在线资源站资源站| 亚洲精品资源美女情侣酒店| 中文字幕a级片| 亚洲精品乱码久久久久久日本蜜臀| 国产成人精品综合久久久久99 | 97热在线精品视频在线观看| 九九在线视频| 91精品婷婷国产综合久久竹菊| 精品97人妻无码中文永久在线| caoporn国产一区二区| 日日碰狠狠躁久久躁婷婷| 91日韩视频| 国产亚洲一区二区三区在线播放| 日韩免费福利视频| 久久高清视频免费| 青青久草在线| 欧美一级理论片| 51国产偷自视频区视频| 日韩一区日韩二区| 亚洲国产综合视频| 97精品资源在线观看| 亚洲少妇30p| 亚洲制服丝袜在线播放| 久久精品国产第一区二区三区| 99久久久精品视频| 狠狠做深爱婷婷综合一区| caoporn国产精品免费公开| 欧美xo影院| 欧美成人在线网站| 国产女主播在线写真| 精品国产一区二区三区四区四| 国产精品午夜一区二区| 亚洲国产毛片aaaaa无费看| jizz中文字幕| 99精品视频在线观看免费| 亚洲免费999| 日韩在线观看一区二区| 成人一级生活片| 欧美jizz| 免费国产在线精品一区二区三区| 深夜激情久久| 成人免费网站在线| 青青热久免费精品视频在线18| 欧美激情伊人电影| 黄色免费网站在线| 尤物九九久久国产精品的特点| 欧美熟女一区二区| 91精品国产综合久久香蕉的特点| 久久人人爽人人爽人人片av免费| 亚洲va在线va天堂| 中文字幕另类日韩欧美亚洲嫩草| 国产女主播视频一区二区| 黄色激情在线观看| 国产suv精品一区二区6| 亚洲图片 自拍偷拍| 免费一级欧美片在线观看| 粗暴91大变态调教| 亚洲一区二区三区高清不卡| 久青草视频在线播放| 在线精品国产| 秋霞在线一区二区| 99精品网站| 欧美aaa在线观看| 欧美xxxx中国| 自拍偷拍一区二区三区| 亚洲澳门在线| 激情图片qvod| 欧美欧美全黄| 成人av在线不卡| 亚洲国产精品第一区二区三区| 日韩久久久久久久久久久久| 欧美精品黄色| 国产曰肥老太婆无遮挡| 亚洲无线一线二线三线区别av| 成人av在线不卡| 一区二区三区福利| 1024av视频| 日韩激情在线观看| 狠狠热免费视频| 麻豆国产精品777777在线| 久久99爱视频| 国产一区二区免费在线| 69久久精品无码一区二区| 国产成人亚洲精品狼色在线| 91精品又粗又猛又爽| 99久久国产综合色|国产精品| 国产男女猛烈无遮挡a片漫画| 久久久久久一级片| 粉嫩精品久久99综合一区| 亚洲欧洲日产国产综合网| 欧产日产国产v| 亚洲成av人片一区二区梦乃| 免费看日批视频| 欧美性色欧美a在线播放| 国产一区二区波多野结衣| 欧美电影免费提供在线观看| 午夜在线观看视频18| 亚洲午夜精品久久久久久久久久久久 | 精品一区在线看| 在线观看网站黄| 99免费精品视频| 亚洲精品视频网址| 亚洲激情中文1区| 精品成人av一区二区在线播放| 欧美亚洲尤物久久| 精品国产亚洲AV| 亚洲免费一在线| 国产精品实拍| 4438全国亚洲精品在线观看视频| 久久久久伊人| 国产精品久久久久久免费观看| 免费观看不卡av| 蜜臀在线免费观看| 日日骚欧美日韩| 乱码一区二区三区| 国产女人aaa级久久久级| 亚洲国产美女视频| 一本色道a无线码一区v| av中文字幕播放| 国产一区二区三区毛片| 国产区美女在线| 国产精品黄页免费高清在线观看| 伊人www22综合色| 伊人情人网综合| 亚洲永久字幕| 人妻精品久久久久中文字幕69| 国产日本欧洲亚洲| 国产成人精品亚洲男人的天堂| 欧美日韩精品二区第二页| 亚洲色图欧美视频| 欧美床上激情在线观看| 成人啊v在线| 国产一区在线免费| 91精品秘密在线观看| 国产a级片免费观看| 成人av电影免费在线播放| 国产黄色小视频网站| 欧美日韩中文字幕日韩欧美| 性猛交xxxx乱大交孕妇印度| 中文字幕国产亚洲| 91精品论坛| 国产美女精品久久久| 亚洲综合中文| 亚洲欧美自偷自拍另类| 久久影院午夜片一区| 国产精品2020| 日韩精品综合一本久道在线视频| 91网在线播放| 国产精品91在线| 日韩成人午夜| 国产精品专区在线| 国产99精品国产| 欧美精品成人久久| 7777精品伊人久久久大香线蕉 | 亚洲一区二区三区四区精品| 日本一区二区综合亚洲| 一级黄色av片| 亚洲欧美日韩综合| 麻豆mv在线看| 九九九九久久久久| 99香蕉国产精品偷在线观看 | 国产精品成人午夜| 中文字幕日韩经典| 中文字幕日韩欧美精品在线观看| 欧美xx视频| 日韩av高清在线播放| 老司机免费视频久久| xxxx日本黄色| 欧美日韩亚洲综合在线| 777电影在线观看| 成人黄色激情网| 在线中文字幕亚洲| 美女日批在线观看| 亚洲一区二区三区自拍| 欧美自拍偷拍第一页| 97精品伊人久久久大香线蕉| 天堂综合网久久| 日韩欧美黄色大片| 国产精品美女久久久久久2018| 一级黄色大片免费| 久久亚洲私人国产精品va| 视频精品一区二区三区| 欧美性潮喷xxxxx免费视频看| 不卡av在线免费观看| 五月婷婷亚洲综合| 亚洲色图第三页| 日韩成人综合网站| 国产精品三级一区二区| 不卡区在线中文字幕| 99精品人妻国产毛片| 中文字幕精品国产| 日韩精品一区二区三区中文字幕| 日韩a级黄色片| www欧美成人18+| 中文字幕欧美人妻精品一区蜜臀| 日韩一级裸体免费视频| 久久av偷拍| 成年人视频观看| 国产精品乱子久久久久| 朝桐光av在线一区二区三区| 97激碰免费视频| av中文字幕一区二区| 天天色天天干天天色| 精品国产91久久久久久老师| 第一视频专区在线| 91精品国产综合久久久久久丝袜| 一本一本久久| 色偷偷www8888| 亚洲福利视频二区| 成人久久网站| 毛片在线播放视频| 中文av一区特黄| 免费观看a视频| 国产精品香蕉国产| 伊人久久婷婷| 毛片视频免费播放| 日韩的一区二区| 3d动漫一区二区三区在线观看| 男人的天堂狠狠干| 成人欧美一区二区三区在线播放| 色噜噜在线播放| 成人激情综合网| 免费视频一区| 久久久久性色av无码一区二区|