日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

什么是重量級鎖?

重量級鎖是一種同步機制,通常與在多線程環境中使用synchronized關鍵字實現同步相關。

由于其實現的開銷和復雜性較高,因此被稱為“重量級”,適合需要更嚴格的同步和并發控制的場景。

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

兩個線程t1和t2正在同時訪問該oneLock()方法。如果t1先獲取鎖并執行其中的同步代碼塊,并且 t2 也嘗試訪問oneLock() 方法,則它將被阻止,因為鎖由t1 持有。

在這種情況下,鎖處于稱為重量級鎖的狀態。

從上面的例子可以看出,t2由于無法獲取鎖,因此被掛起,等待t1釋放鎖后再被喚醒。

線程的掛起和喚醒涉及CPU內的上下文切換,這會產生很大的開銷。

由于這個過程的成本相對較高,具有這種行為的鎖被稱為重量級鎖。

什么是輕量級鎖

輕量級鎖是一種同步機制,旨在減輕與傳統重量級鎖(例如 JAVA synchronized關鍵字提供的鎖)相關的性能開銷。

繼續前面的示例,讓我們現在考慮t1和t2交替執行oneLock()方法。

在這種情況下,t1和t2不需要阻塞,因為它們之間沒有爭用。換句話說,不需要重量級的鎖。

當線程交替執行臨界區而不發生爭用時,這種場景下使用的鎖被稱為輕量級鎖。

輕量級鎖相對于重量級鎖的優點:

1、每次加鎖只需要一次CAS操作。
2. 無需分配ObjectMonitor對象。
3、線程不需要被掛起或喚醒。

什么是偏向鎖?

在只有一個線程(假設 t1)一致執行oneLock()方法的情況下,使用輕量級鎖t1在每次獲取鎖時執行 CAS 操作。這可能會導致一些性能開銷。

于是,偏向鎖的概念就出現了。

當鎖偏向特定線程時,該線程可以再次獲取鎖,而無需進行 CAS 操作。相反,簡單的比較就足以獲得鎖。這個過程非常高效。

偏向鎖相比輕量級鎖的優點:

  • 當同一個線程多次獲取鎖時,不需要再次執行 CAS 操作。簡單的比較就足夠了。

怎樣加鎖?

讓我們從源代碼的角度深入研究一下 Java 中這些鎖是如何實現的。

鎖的本質在于共享變量,所以問題的關鍵是如何訪問這些共享變量。了解這一點就了解了這三種鎖的演變過程的一半。

接下來我將從源碼分析的角度重點介紹一下這些信息。

既然我們處理的是鎖,自然就涉及到鎖的獲取和釋放操作,而在偏向鎖的情況下,還有鎖撤銷操作。

對象頭是Java對象在內存中布局的一部分,用于存儲對象的元數據信息和鎖定狀態。

從C源碼看Java同步鎖機制的演變

 

在深入源碼之前,我們先推測一下線程 t1 獲取偏向鎖的過程:

  1. 首先檢查Mark word中的線程ID是否有值。
  2. 如果沒有,則意味著還沒有線程獲得鎖。本例中,直接將t1的線程ID記錄到Mark Word中。多個線程可能會嘗試同時修改Mark Word,因此需要CAS操作來修改Mark Word。
  3. 如果已經有一個 ID 值,那么有兩種可能性:
  • 如果該ID是t1的ID,那么本次鎖獲取就是一個可重入的過程,t1可以直接獲取鎖。
  • 如果該ID不是t1的ID,則意味著另一個線程已經獲取了鎖。這種情況下,t1需要經過撤銷過程來獲取鎖。
CASE(_monitorenter): {
// 1. 獲取對象頭,表示為“oop”(普通對象指針)。
  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 ) {
// 2. 遍歷線程棧找到對應的可用BasicObjectLock。  
    if (most_recent->obj() == NULL) entry = most_recent;
    else if (most_recent->obj() == lockee) break;
    most_recent++;
  }
  if (entry != NULL) {
// 3. BasicObjectLock 的 _obj 字段指向 oop。
    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;
// 檢查是否支持偏向鎖定。
    if (mark->has_bias_pattern()) {
      uintptr_t thread_ident;
      uintptr_t anticipated_bias_locking_value;
      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. 如果不支持偏向鎖
        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) {
// 7. 如果epoch已過期,則使用當前線程的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);
        }
        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);

        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) {
        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-8標注的內容進行詳細解釋。

# 1.oop代表對象頭,包含Mark Word和Klass Word。

# 2.BasicObjectLock的結構如下:

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

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

BasicObjectLock是著名的Lock Record的實現,它包括兩個元素:

  1. 存儲Mark Word的移位頭_displaced_header。
  2. 指向對象頭的指針:_obj。

# 3、將Lock Record中的_obj字段賦值給lockee,代表對象頭。

# 4. 從對象頭lockee中,檢索Klass Word,它是指向Klass類型的指針。在Klass類內部,有一個名為_prototype_header的字段,它也代表Mark Word。它存儲偏向鎖定標志之類的信息。

在此步驟中,提取此信息并將其與當前線程 ID 連接起來。

然后與對象頭中的Mark Word 執行XOR 運算。目標是識別不同的位。

后續步驟涉及確定Mark Word的哪些特定部分不相等,從而導致不同的處理邏輯。

# 5. 如果上面的異或運算結果相等,則表明Mark Word中包含當前線程ID,并且epoch和偏向鎖標志一致。

這表明該鎖已經被當前線程持有,表明是可重入的。由于線程已經擁有鎖,因此不需要采取進一步的操作。

# 6. 觀察Mark Word中的偏向鎖標志與Klass中的偏向鎖標志不一致,并且考慮到Mark Word已經被識別為具有偏向鎖,因此可以推斷Klass不再支持偏向鎖。

鑒于不支持偏向鎖定,標記字被修改以反映解鎖狀態。這為進一步升級到輕量級鎖定或重量級鎖定做好了準備。

# 7. 在識別出 Mark Word 中的紀元與 Klass 中的標記之間的差異后,可以推斷發生了批量重新偏置。這種情況下,直接修改Mark Word,使其偏向當前線程。

# 8、如果以上條件都不滿足,則表明是匿名偏向鎖(不偏向任何線程的偏向鎖)。在這種情況下,會嘗試直接修改Mark Word以偏向當前線程

總結

  • 每次線程嘗試獲取鎖時,都需要關聯一個鎖記錄,并將“_obj”指針設置為對象頭。這在鎖定記錄和對象頭之間建立了連接。
  • 一旦線程成功將自己的線程ID寫入Mark Word,就表明該線程已經獲得了偏向鎖。

在偏向鎖狀態下,鎖記錄和對象頭之間建立了關系。這種關系由指向對象頭的鎖定記錄的 _obj 字段表示。

從C源碼看Java同步鎖機制的演變

 

我們回顧一下線程t1和t2獲取偏向鎖的過程:

  1. 線程 t1 嘗試獲取鎖。最初,鎖處于匿名偏向狀態, T1成功獲取鎖。
  2. 線程 t1 嘗試再次獲取鎖。由于它已經持有鎖,所以他會來獲取可重入鎖。
  3. 同時,線程 t2 嘗試獲取鎖。由于 t1 當前持有鎖定,因此 t2 會鎖撤銷。

鎖撤銷

如果嘗試獲取偏向鎖不成功,鎖將恢復為未鎖定狀態,然后升級為輕量級鎖。此過程稱為偏向鎖撤銷。

#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”,發生在未等待安全點而撤銷偏向鎖時。在這個過程中,偏向鎖被直接撤銷,并且對象的標記字被更新以反映新的狀態,而不需要安全點來保證一致的狀態轉換。

當發生非安全點撤銷時,偏向鎖的狀態從偏向變為正常或可重偏向。

如果它更改為可重偏向狀態,則意味著如果另一個線程尋求該鎖,該鎖可以再次偏向。

這允許更快、更有效的鎖定轉換,因為如果另一個線程在撤銷后不久獲取該鎖,則該鎖可能會跳過中間狀態并直接進入偏向狀態。

從本質上講,非安全點撤銷減少了等待安全點的需要,并實現了更靈活、響應更靈敏的方法來撤銷偏向鎖,從而提高了性能并減少了某些場景下的鎖爭用。

批量重新偏向和批量撤銷

經過以上分析,我們了解到以下幾點:

  • 當一個線程持有偏向鎖,而另一個線程試圖獲取該鎖時,需要撤銷該鎖。
  • 撤銷過程首先嘗試使用CAS在非安全點將Mark Word更改為解鎖狀態。如果仍然無法實現撤銷,則可以考慮在安全點執行撤銷的選項,盡管在安全點執行撤銷的效率相對較低。

因此,偏向鎖引入了批量重偏向和批量撤銷的概念。

當對象的鎖被撤銷的次數達到一定閾值時,例如20次,就會觸發批量重偏邏輯。

這涉及到修改 Klass 中的標記以及當前使用的該類型鎖的 Mark Word 中的標記。

當線程嘗試獲取偏向鎖時,它會將當前對象的紀元值與 Klass 中的標記值進行比較。

如果不相等,則認為鎖已過期。在這種情況下,允許線程直接CAS修改Mark Word以偏向當前線程,避免撤銷邏輯。這對應于偏向鎖進入最初討論中的分析標簽(7)。

同樣,當撤銷次數達到40次時,就認為該對象不再適合偏向鎖。

因此,Klass 中的偏向鎖標志發生更改,以指示不再支持偏向鎖。

當線程嘗試獲取偏向鎖時,它會檢查 Klass 中的偏向鎖標志。如果不再允許偏差,則表明批次撤銷較早發生。

在這種情況下,允許線程直接CAS將Mark Word修改為解鎖狀態,避免了撤銷邏輯。這對應于偏向鎖進入最初討論中的分析標簽(6)。

批量重新偏向和批量撤銷是旨在提高偏向鎖定性能的優化。

鎖釋放


#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();
// 將鎖定記錄中的_obj字段設置為null
      most_recent->set_obj(NULL);
// 這是輕量級鎖的釋放。
      UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
    }
    most_recent++;
  }
  ...
}

您可能已經注意到,Mark Word 沒有改變;它仍然偏向于前一個線程。然而,鎖還沒有被釋放。事實上,當線程退出臨界區時,它不會釋放偏向鎖。

原因是:

當再次需要鎖時,簡單的按位比較就可以快速判斷是否是可重入獲取。這意味著不需要每次都執行CAS操作就可以高效地獲取鎖。這種效率是偏向鎖在只有一個線程訪問鎖的場景下的核心優勢。

總結

  1. 偏向鎖中的“鎖”指的是Mark Word。修改Mark Word是獲取鎖所必需的,由于潛在的多線程爭用,這可能會涉及CAS操作。
  2. 由于撤銷操作在安全點執行時效率可能較低,并且多次撤銷會進一步影響效率,因此引入了批量重偏和撤銷機制。
  3. 偏向鎖的可重入計數取決于線程堆棧中存在的鎖記錄的數量。
  4. 如果偏向鎖撤銷失敗,鎖最終會升級為輕量級鎖。
  5. 退出時,偏向鎖不會修改Mark Word,也就是說鎖沒有被釋放。

未完待續。。。。。

分享到:
標簽:Java
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定