Java -- 偏向鎖、輕量級鎖、自旋鎖、重量級鎖

来源:http://www.cnblogs.com/lzh-blogs/archive/2017/09/05/7477157.html
-Advertisement-
Play Games

之前做過一個測試,詳情見這篇文章《多線程 +1操作的幾種實現方式,及效率對比》,當時對這個測試結果很疑惑,反覆執行過多次,發現結果是一樣的: 1. 單線程下synchronized效率最高(當時感覺它的效率應該是最差才對); 2. AtomicInteger效率最不穩定,不同併發情況下表現不一樣:短 ...


之前做過一個測試,詳情見這篇文章《多線程 +1操作的幾種實現方式,及效率對比》,當時對這個測試結果很疑惑,反覆執行過多次,發現結果是一樣的: 
1. 單線程下synchronized效率最高(當時感覺它的效率應該是最差才對); 
2. AtomicInteger效率最不穩定,不同併發情況下表現不一樣:短時間低併發下,效率比synchronized高,有時甚至比LongAdder還高出一點,但是高併發下,性能還不如synchronized,不同情況下性能表現很不穩定; 
3. LongAdder性能穩定,在各種併發情況下表現都不錯,整體表現最好,短時間的低併發下比AtomicInteger性能差一點,長時間高併發下性能最高(可以讓AtomicInteger下臺了);

這篇文章我們就去揭秘,為什麼會是這個測試結果!

理解鎖的基礎知識

如果想要透徹的理解Java鎖的來龍去脈,需要先瞭解以下基礎知識。

基礎知識之一:鎖的類型

鎖從巨集觀上分類,分為悲觀鎖與樂觀鎖。

樂觀鎖

樂觀鎖是一種樂觀思想,即認為讀多寫少,遇到併發寫的可能性低,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,採取在寫時先讀出當前版本號,然後加鎖操作(比較跟上一次的版本號,如果一樣則更新),如果失敗則要重覆讀-比較-寫的操作。

java中的樂觀鎖基本都是通過CAS操作實現的,CAS是一種更新的原子操作,比較當前值跟傳入值是否一樣,一樣則更新,否則失敗。

悲觀鎖

悲觀鎖是就是悲觀思想,即認為寫多,遇到併發寫的可能性高,每次去拿數據的時候都認為別人會修改,所以每次在讀寫數據的時候都會上鎖,這樣別人想讀寫這個數據就會block直到拿到鎖。java中的悲觀鎖就是Synchronized,AQS框架下的鎖則是先嘗試cas樂觀鎖去獲取鎖,獲取不到,才會轉換為悲觀鎖,如RetreenLock。

基礎知識之二:java線程阻塞的代價

java的線程是映射到操作系統原生線程之上的,如果要阻塞或喚醒一個線程就需要操作系統介入,需要在戶態與核心態之間切換,這種切換會消耗大量的系統資源,因為用戶態與內核態都有各自專用的記憶體空間,專用的寄存器等,用戶態切換至內核態需要傳遞給許多變數、參數給內核,內核也需要保護好用戶態在切換時的一些寄存器值、變數等,以便內核態調用結束後切換回用戶態繼續工作。

  1. 如果線程狀態切換是一個高頻操作時,這將會消耗很多CPU處理時間;
  2. 如果對於那些需要同步的簡單的代碼塊,獲取鎖掛起操作消耗的時間比用戶代碼執行的時間還要長,這種同步策略顯然非常糟糕的。

synchronized會導致爭用不到鎖的線程進入阻塞狀態,所以說它是java語言中一個重量級的同步操縱,被稱為重量級鎖,為了緩解上述性能問題,JVM從1.5開始,引入了輕量鎖與偏向鎖,預設啟用了自旋鎖,他們都屬於樂觀鎖。

明確java線程切換的代價,是理解java中各種鎖的優缺點的基礎之一。

基礎知識之三:markword

在介紹java鎖之前,先說下什麼是markword,markword是java對象數據結構中的一部分,要詳細瞭解java對象的結構可以點擊這裡,這裡只做markword的詳細介紹,因為對象的markword和java各種類型的鎖密切相關;

markword數據的長度在32位和64位的虛擬機(未開啟壓縮指針)中分別為32bit和64bit,它的最後2bit是鎖狀態標誌位,用來標記當前對象的狀態,對象的所處的狀態,決定了markword存儲的內容,如下表所示:

狀態標誌位存儲內容
未鎖定 01 對象哈希碼、對象分代年齡
輕量級鎖定 00 指向鎖記錄的指針
膨脹(重量級鎖定) 10 執行重量級鎖定的指針
GC標記 11 空(不需要記錄信息)
可偏向 01 偏向線程ID、偏向時間戳、對象分代年齡

32位虛擬機在不同狀態下markword結構如下圖所示:

這裡寫圖片描述

瞭解了markword結構,有助於後面瞭解java鎖的加鎖解鎖過程;

小結

前面提到了java的4種鎖,他們分別是重量級鎖、自旋鎖、輕量級鎖和偏向鎖, 
不同的鎖有不同特點,每種鎖只有在其特定的場景下,才會有出色的表現,java中沒有哪種鎖能夠在所有情況下都能有出色的效率,引入這麼多鎖的原因就是為了應對不同的情況;

前面講到了重量級鎖是悲觀鎖的一種,自旋鎖、輕量級鎖與偏向鎖屬於樂觀鎖,所以現在你就能夠大致理解了他們的適用範圍,但是具體如何使用這幾種鎖呢,就要看後面的具體分析他們的特性;

java中的鎖

自旋鎖

自旋鎖原理非常簡單,如果持有鎖的線程能在很短時間內釋放鎖資源,那麼那些等待競爭鎖的線程就不需要做內核態和用戶態之間的切換進入阻塞掛起狀態,它們只需要等一等(自旋),等持有鎖的線程釋放鎖後即可立即獲取鎖,這樣就避免用戶線程和內核的切換的消耗

但是線程自旋是需要消耗cup的,說白了就是讓cup在做無用功,線程不能一直占用cup自旋做無用功,所以需要設定一個自旋等待的最大時間。

如果持有鎖的線程執行的時間超過自旋等待的最大時間扔沒有釋放鎖,就會導致其它爭用鎖的線程在最大等待時間內還是獲取不到鎖,這時爭用線程會停止自旋進入阻塞狀態。

自旋鎖的優缺點

自旋鎖儘可能的減少線程的阻塞,這對於鎖的競爭不激烈,且占用鎖時間非常短的代碼塊來說性能能大幅度的提升,因為自旋的消耗會小於線程阻塞掛起操作的消耗!

但是如果鎖的競爭激烈,或者持有鎖的線程需要長時間占用鎖執行同步塊,這時候就不適合使用自旋鎖了,因為自旋鎖在獲取鎖前一直都是占用cpu做無用功,占著XX不XX,線程自旋的消耗大於線程阻塞掛起操作的消耗,其它需要cup的線程又不能獲取到cpu,造成cpu的浪費。

自旋鎖時間閾值

自旋鎖的目的是為了占著CPU的資源不釋放,等到獲取到鎖立即進行處理。但是如何去選擇自旋的執行時間呢?如果自旋執行時間太長,會有大量的線程處於自旋狀態占用CPU資源,進而會影響整體系統的性能。因此自旋的周期選的額外重要!

JVM對於自旋周期的選擇,jdk1.5這個限度是一定的寫死的,在1.6引入了適應性自旋鎖,適應性自旋鎖意味著自旋的時間不在是固定的了,而是由前一次在同一個鎖上的自旋時間以及鎖的擁有者的狀態來決定,基本認為一個線程上下文切換的時間是最佳的一個時間,同時JVM還針對當前CPU的負荷情況做了較多的優化

  1. 如果平均負載小於CPUs則一直自旋

  2. 如果有超過(CPUs/2)個線程正在自旋,則後來線程直接阻塞

  3. 如果正在自旋的線程發現Owner發生了變化則延遲自旋時間(自旋計數)或進入阻塞

  4. 如果CPU處於節電模式則停止自旋

  5. 自旋時間的最壞情況是CPU的存儲延遲(CPU A存儲了一個數據,到CPU B得知這個數據直接的時間差)

  6. 自旋時會適當放棄線程優先順序之間的差異

自旋鎖的開啟

JDK1.6中-XX:+UseSpinning開啟; 
JDK1.7後,去掉此參數,由jvm控制;

重量級鎖Synchronized

Synchronized的作用

在JDK1.5之前都是使用synchronized關鍵字保證同步的,Synchronized的作用相信大家都已經非常熟悉了;

它可以把任意一個非NULL的對象當作鎖。

  1. 作用於方法時,鎖住的是對象的實例(this);
  2. 當作用於靜態方法時,鎖住的是Class實例,又因為Class的相關數據存儲在永久帶PermGen(jdk1.8則是metaspace),永久帶是全局共用的,因此靜態方法鎖相當於類的一個全局鎖,會鎖所有調用該方法的線程;
  3. synchronized作用於一個對象實例時,鎖住的是所有以該對象為鎖的代碼塊。

Synchronized的實現

實現如下圖所示;

這裡寫圖片描述

它有多個隊列,當多個線程一起訪問某個對象監視器的時候,對象監視器會將這些線程存儲在不同的容器中。

  1. Contention List:競爭隊列,所有請求鎖的線程首先被放在這個競爭隊列中;

  2. Entry List:Contention List中那些有資格成為候選資源的線程被移動到Entry List中;

  3. Wait Set:哪些調用wait方法被阻塞的線程被放置在這裡;

  4. OnDeck:任意時刻,最多只有一個線程正在競爭鎖資源,該線程被成為OnDeck;

  5. Owner:當前已經獲取到所資源的線程被稱為Owner;

  6. !Owner:當前釋放鎖的線程。

JVM每次從隊列的尾部取出一個數據用於鎖競爭候選者(OnDeck),但是併發情況下,ContentionList會被大量的併發線程進行CAS訪問,為了降低對尾部元素的競爭,JVM會將一部分線程移動到EntryList中作為候選競爭線程。Owner線程會在unlock時,將ContentionList中的部分線程遷移到EntryList中,並指定EntryList中的某個線程為OnDeck線程(一般是最先進去的那個線程)。Owner線程並不直接把鎖傳遞給OnDeck線程,而是把鎖競爭的權利交給OnDeck,OnDeck需要重新競爭鎖。這樣雖然犧牲了一些公平性,但是能極大的提升系統的吞吐量,在JVM中,也把這種選擇行為稱之為“競爭切換”。

OnDeck線程獲取到鎖資源後會變為Owner線程,而沒有得到鎖資源的仍然停留在EntryList中。如果Owner線程被wait方法阻塞,則轉移到WaitSet隊列中,直到某個時刻通過notify或者notifyAll喚醒,會重新進去EntryList中。

處於ContentionList、EntryList、WaitSet中的線程都處於阻塞狀態,該阻塞是由操作系統來完成的(Linux內核下採用pthread_mutex_lock內核函數實現的)。

Synchronized是非公平鎖。 Synchronized線上程進入ContentionList時,等待的線程會先嘗試自旋獲取鎖,如果獲取不到就進入ContentionList,這明顯對於已經進入隊列的線程是不公平的,還有一個不公平的事情就是自旋獲取鎖的線程還可能直接搶占OnDeck線程的鎖資源。

偏向鎖

Java偏向鎖(Biased Locking)是Java6引入的一項多線程優化。 
偏向鎖,顧名思義,它會偏向於第一個訪問鎖的線程,如果在運行過程中,同步鎖只有一個線程訪問,不存在多線程爭用的情況,則線程是不需要觸發同步的,這種情況下,就會給線程加一個偏向鎖。 
如果在運行過程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會被掛起,JVM會消除它身上的偏向鎖,將鎖恢復到標準的輕量級鎖。

它通過消除資源無競爭情況下的同步原語,進一步提高了程式的運行性能。

偏向鎖的實現

偏向鎖獲取過程:

  1. 訪問Mark Word中偏向鎖的標識是否設置成1,鎖標誌位是否為01,確認為可偏向狀態。

  2. 如果為可偏向狀態,則測試線程ID是否指向當前線程,如果是,進入步驟5,否則進入步驟3。

  3. 如果線程ID並未指向當前線程,則通過CAS操作競爭鎖。如果競爭成功,則將Mark Word中線程ID設置為當前線程ID,然後執行5;如果競爭失敗,執行4。

  4. 如果CAS獲取偏向鎖失敗,則表示有競爭。當到達全局安全點(safepoint)時獲得偏向鎖的線程被掛起,偏向鎖升級為輕量級鎖,然後被阻塞在安全點的線程繼續往下執行同步代碼。(撤銷偏向鎖的時候會導致stop the word)

  5. 執行同步代碼。

註意:第四步中到達安全點safepoint會導致stop the word,時間很短。

偏向鎖的釋放:

偏向鎖的撤銷在上述第四步驟中有提到。偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖,線程不會主動去釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有位元組碼正在執行),它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處於被鎖定狀態,撤銷偏向鎖後恢復到未鎖定(標誌位為“01”)或輕量級鎖(標誌位為“00”)的狀態。

偏向鎖的適用場景

始終只有一個線程在執行同步塊,在它沒有執行完釋放鎖之前,沒有其它線程去執行同步塊,在鎖無競爭的情況下使用,一旦有了競爭就升級為輕量級鎖,升級為輕量級鎖的時候需要撤銷偏向鎖,撤銷偏向鎖的時候會導致stop the word操作; 
在有鎖的競爭時,偏向鎖會多做很多額外操作,尤其是撤銷偏向所的時候會導致進入安全點,安全點會導致stw,導致性能下降,這種情況下應當禁用;

查看停頓–安全點停頓日誌

要查看安全點停頓,可以打開安全點日誌,通過設置JVM參數 -XX:+PrintGCApplicationStoppedTime 會打出系統停止的時間,添加-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 這兩個參數會列印出詳細信息,可以查看到使用偏向鎖導致的停頓,時間非常短暫,但是爭用嚴重的情況下,停頓次數也會非常多;

註意:安全點日誌不能一直打開: 
1. 安全點日誌預設輸出到stdout,一是stdout日誌的整潔性,二是stdout所重定向的文件如果不在/dev/shm,可能被鎖。 
2. 對於一些很短的停頓,比如取消偏向鎖,列印的消耗比停頓本身還大。 
3. 安全點日誌是在安全點內列印的,本身加大了安全點的停頓時間。

所以安全日誌應該只在問題排查時打開。 
如果在生產系統上要打開,再再增加下麵四個參數: 
-XX:+UnlockDiagnosticVMOptions -XX: -DisplayVMOutput -XX:+LogVMOutput -XX:LogFile=/dev/shm/vm.log 
打開Diagnostic(只是開放了更多的flag可選,不會主動激活某個flag),關掉輸出VM日誌到stdout,輸出到獨立文件,/dev/shm目錄(記憶體文件系統)。

這裡寫圖片描述

此日誌分三部分: 
第一部分是時間戳,VM Operation的類型 
第二部分是線程概況,被中括弧括起來 
total: 安全點里的匯流排程數 
initially_running: 安全點時開始時正在運行狀態的線程數 
wait_to_block: 在VM Operation開始前需要等待其暫停的線程數

第三部分是到達安全點時的各個階段以及執行操作所花的時間,其中最重要的是vmop

  • spin: 等待線程響應safepoint號召的時間;
  • block: 暫停所有線程所用的時間;
  • sync: 等於 spin+block,這是從開始到進入安全點所耗的時間,可用於判斷進入安全點耗時;
  • cleanup: 清理所用時間;
  • vmop: 真正執行VM Operation的時間。

可見,那些很多但又很短的安全點,全都是RevokeBias, 高併發的應用會禁用掉偏向鎖。

jvm開啟/關閉偏向鎖

  • 開啟偏向鎖:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
  • 關閉偏向鎖:-XX:-UseBiasedLocking

輕量級鎖

輕量級鎖是由偏向所升級來的,偏向鎖運行在一個線程進入同步塊的情況下,當第二個線程加入鎖爭用的時候,偏向鎖就會升級為輕量級鎖; 
輕量級鎖的加鎖過程:

  1. 在代碼進入同步塊的時候,如果同步對象鎖狀態為無鎖狀態(鎖標誌位為“01”狀態,是否為偏向鎖為“0”),虛擬機首先將在當前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的Mark Word的拷貝,官方稱之為 Displaced Mark Word。這時候線程堆棧與對象頭的狀態如圖: 
      這裡寫圖片描述所示。

  2. 拷貝對象頭中的Mark Word複製到鎖記錄中;

  3. 拷貝成功後,虛擬機將使用CAS操作嘗試將對象的Mark Word更新為指向Lock Record的指針,並將Lock record里的owner指針指向object mark word。如果更新成功,則執行步驟4,否則執行步驟5。

  4. 如果這個更新動作成功了,那麼這個線程就擁有了該對象的鎖,並且對象Mark Word的鎖標誌位設置為“00”,即表示此對象處於輕量級鎖定狀態,這時候線程堆棧與對象頭的狀態如圖所示。 
      這裡寫圖片描述

  5. 如果這個更新操作失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀,如果是就說明當前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行。否則說明多個線程競爭鎖,輕量級鎖就要膨脹為重量級鎖,鎖標誌的狀態值變為“10”,Mark Word中存儲的就是指向重量級鎖(互斥量)的指針,後面等待鎖的線程也要進入阻塞狀態。 而當前線程便嘗試使用自旋來獲取鎖,自旋就是為了不讓線程阻塞,而採用迴圈去獲取鎖的過程。

輕量級鎖的釋放

釋放鎖線程視角:由輕量鎖切換到重量鎖,是發生在輕量鎖釋放鎖的期間,之前在獲取鎖的時候它拷貝了鎖對象頭的markword,在釋放鎖的時候如果它發現在它持有鎖的期間有其他線程來嘗試獲取鎖了,並且該線程對markword做了修改,兩者比對發現不一致,則切換到重量鎖。

因為重量級鎖被修改了,所有display mark word和原來的markword不一樣了。

怎麼補救,就是進入mutex前,compare一下obj的markword狀態。確認該markword是否被其他線程持有。

此時如果線程已經釋放了markword,那麼通過CAS後就可以直接進入線程,無需進入mutex,就這個作用。

嘗試獲取鎖線程視角:如果線程嘗試獲取鎖的時候,輕量鎖正被其他線程占有,那麼它就會修改markword,修改重量級鎖,表示該進入重量鎖了。

還有一個註意點:等待輕量鎖的線程不會阻塞,它會一直自旋等待鎖,並如上所說修改markword。

這就是自旋鎖,嘗試獲取鎖的線程,在沒有獲得鎖的時候,不被掛起,而轉而去執行一個空迴圈,即自旋。在若幹個自旋後,如果還沒有獲得鎖,則才被掛起,獲得鎖,則執行代碼。

總結

這裡寫圖片描述

上面幾種鎖都是JVM自己內部實現,當我們執行synchronized同步塊的時候jvm會根據啟用的鎖和當前線程的爭用情況,決定如何執行同步操作;

在所有的鎖都啟用的情況下線程進入臨界區時會先去獲取偏向鎖,如果已經存在偏向鎖了,則會嘗試獲取輕量級鎖,如果以上兩種都失敗,則啟用自旋鎖,如果自旋也沒有獲取到鎖,則使用重量級鎖,沒有獲取到鎖的線程阻塞掛起,直到持有鎖的線程執行完同步塊喚醒他們;

偏向鎖是在無鎖爭用的情況下使用的,也就是同步開在當前線程沒有執行完之前,沒有其它線程會執行該同步快,一旦有了第二個線程的爭用,偏向鎖就會升級為輕量級鎖,一點有兩個以上線程爭用,就會升級為重量級鎖;

如果線程爭用激烈,那麼應該禁用偏向鎖。

鎖優化

以上介紹的鎖不是我們代碼中能夠控制的,但是借鑒上面的思想,我們可以優化我們自己線程的加鎖操作;

減少鎖的時間

不需要同步執行的代碼,能不放在同步快裡面執行就不要放在同步快內,可以讓鎖儘快釋放;

減少鎖的粒度

它的思想是將物理上的一個鎖,拆成邏輯上的多個鎖,增加並行度,從而降低鎖競爭。它的思想也是用空間來換時間;

java中很多數據結構都是採用這種方法提高併發操作的效率:

ConcurrentHashMap

java中的ConcurrentHashMap在jdk1.8之前的版本,使用一個Segment 數組

Segment< K,V >[] segments
  • 1
  • 1

Segment繼承自ReenTrantLock,所以每個Segment就是個可重入鎖,每個Segment 有一個HashEntry< K,V >數組用來存放數據,put操作時,先確定往哪個Segment放數據,只需要鎖定這個Segment,執行put,其它的Segment不會被鎖定;所以數組中有多少個Segment就允許同一時刻多少個線程存放數據,這樣增加了併發能力。

LongAdder

LongAdder 實現思路也類似ConcurrentHashMap,LongAdder有一個根據當前併發狀況動態改變的Cell數組,Cell對象裡面有一個long類型的value用來存儲值; 
開始沒有併發爭用的時候或者是cells數組正在初始化的時候,會使用cas來將值累加到成員變數的base上,在併發爭用的情況下,LongAdder會初始化cells數組,在Cell數組中選定一個Cell加鎖,數組有多少個cell,就允許同時有多少線程進行修改,最後將數組中每個Cell中的value相加,在加上base的值,就是最終的值;cell數組還能根據當前線程爭用情況進行擴容,初始長度為2,每次擴容會增長一倍,直到擴容到大於等於cpu數量就不再擴容;

LinkedBlockingQueue

LinkedBlockingQueue也體現了這樣的思想,在隊列頭入隊,在隊列尾出隊,入隊和出隊使用不同的鎖,相對於LinkedBlockingArray只有一個鎖效率要高;

拆鎖的粒度不能無限拆,最多可以將一個鎖拆為當前cup數量個鎖即可;

鎖粗化

大部分情況下我們是要讓鎖的粒度最小化,鎖的粗化則是要增大鎖的粒度; 
在以下場景下需要粗話鎖的粒度: 
假如有一個迴圈,迴圈內的操作需要加鎖,我們應該把鎖放到迴圈外面,否則每次進出迴圈,都進出一次臨界區,效率是非常差的;

使用讀寫鎖

ReentrantReadWriteLock 是一個讀寫鎖,讀操作加讀鎖,可以併發讀,寫操作使用寫鎖,只能單線程寫;

消除緩存行的偽共用

除了我們再代碼中使用的同步鎖和jvm自己內置的同步鎖外,還有一種隱藏的鎖就是緩存行,它也被稱為性能殺手。 
在多核cup的處理器中,每個cup都有自己獨占的一級緩存、二級緩存,甚至還有一個共用的三級緩存,為了提高性能,cpu讀取數據是以緩存行為最小單元讀取的;32位的cpu緩存行為32位元組,64位cup的緩存行為64位元組,這就導致了一些問題。 
例如,多個不需要同步的變數進入同一個緩存行被載入到某個cup-1私有的緩存中,相當於是對主記憶體變數的一個拷貝,也相當於變相的將在同一個緩存行中的變數加了一把鎖,這個緩存行中任何一個變數發生了變化,當cup-2需要讀取這個緩存行時,就需要先將cup-1中被改變了的整個緩存行更新回主存(即使其它變數沒有更改),然後cup-2才能夠讀取,而cup-2可能需要更改這個緩存行的變數與cpu-1已經更改的緩存行中的變數是不一樣的,所以這相當於給幾個毫不相關的變數加了一把同步鎖; 
為了防止偽共用,不同jdk版本實現方式是不一樣的: 
1. 在jdk1.7之前會 將需要獨占緩存行的變數前後添加一組long類型的變數; 
2. 在jdk1.7因為jvm會將這些沒有用到的變數優化掉,所以採用繼承一個聲明瞭好多long變數的類的方式來實現; 
3. 在jdk1.8中通過添加sun.misc.Contended註解來解決這個問題,若要使該註解有效必須在jvm中添加以下參數: 
-XX:-RestrictContended

sun.misc.Contended註解會在變數前面添加128位元組的padding將當前變數與其他變數進行隔離; 
關於什麼是緩存行,jdk是如何避免緩存行的,網上有非常多的解釋,在這裡就不再深入講解了;

其它方式等待著大家一起補充


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 合集是一個無序的,不重覆的數據組合,它的特點:1. 去重,把一個列表變成合集,就自動去重了。2. 關係測試,測試兩組數據之間的交集、差集、並集等關係。3. 沒有插入功能,只能添加。 4. 以{}的形式出現,類型為set 交集: 交集縮寫: 並集,: 並集縮寫 差集,就是1裡面有而2裡面沒有的數據。: ...
  • 1.for迴圈 2.while迴圈 3.break 4.continue ...
  • 面向過程的程式設計經常用於操作系統的內核,git等,一成不變的流水線式解決一個問題,極大程度降低程式複雜性。 面向對象的程式設計解決了程式的擴展性(類可產生各種各樣的對象,對於新增技能或修改技能可使用方法直接調用),但是可控性差,因為面向對象程式一旦開始就是由對象之間交互來解決問題。 OOD面向對象 ...
  • 關於 Go 中 Map 類型和 Slice 類型的傳遞 Map 類型 先看例子 m1: 結果是 我們再修改如下 m2: 發現結果變成了 要理解這個問題,需要明確在 Go 中不存在引用傳遞,所有的參數傳遞都是值傳遞。 現在再來分析下,如圖: 可能有些人會有疑問,為什麼途中的 m 像是一個指針呢。查看 ...
  • 前面幾篇文章我們學習了怎麼定位元素,同時通過實例也展示了怎麼切換到iframe,怎麼輸入用戶名和密碼,怎麼點擊登錄按鈕,首先我們先回顧一下元素的基本操作。 1.點擊(滑鼠左鍵)頁面按鈕:click() 2.請空輸入框:clear() 3.輸入字元串:send_keys() 4.提交表單:submit ...
  • 第一件事 eclipse設置為自動提示 配置步驟: 1 Window Preferences Java Editor Content Assist 2 “Auto Activation triggers for java”這個選項就是指觸發代碼提示的的選項, 把“.”修改成".abcdefghijk ...
  • apache-tomcat-7.0.52\conf下server.xml文件 <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/> 將埠從8080改成80 <Host a ...
  • 第一種方式:for-in迴圈 OC延續了C語言的for迴圈,在Swift中被徹底改造,我們無法再使用傳統形式的for迴圈了 遍曆數組和字典: 如上遍曆數組使用了2種方式 1、第一種方式是Swift中普通的for迴圈語法,在索引index和遍歷範圍0...6之間用關鍵字in,這裡要註意0...6的表示 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...