PWM學習和使用
1,synchronized關鍵字用法
①修飾程式碼塊,指定加鎖物件,表示進入當前程式碼塊要獲得指定物件的鎖。
②修飾例項方法:對當前物件加鎖,進入同步方法要獲得當前物件的鎖。
③修飾靜態方法,給當前類加鎖,作用於類的所有物件,訪問靜態方法要獲得class鎖。
2,介紹一下synchronized
①,解決多個執行緒訪問資源的同步性,synchronized關鍵字保證它修飾的方法或者程式碼塊在任意時刻只有一個執行緒訪問。
②java早期版本sychronized是重量級鎖,效率較低
因為監視器鎖(monitor)是依賴於底層的作業系統的Mutex Lock
如果要掛起或者喚醒一個執行緒,都需要作業系統幫忙完成,而作業系統實現執行緒之間的切換時需要從使用者態轉換到核心態,
這個狀態之間的轉換需要相對比較長的時間,時間成本相對較高。
③後期優化,引入偏向鎖,輕量級鎖等。
2.1,底層原理:
①java中每一個物件都可以作為鎖,每一個Object對像都有一個Monitor監視器,
②synchronized同步語句塊使用monitorenter和monitorexit指令分別指向同步程式碼塊的開始與結束,當執行monitorenter時,嘗試獲取物件鎖,
物件鎖計數器為0則表示該物件鎖可獲取,獲取後,物件鎖加1.
③當同步方法時,用到ACC_SYNCHRINIZED標識,表示該方法是同步方法。monitorenter和monitorexit指令與ACC_SYNCHRINIZED標識
都是為了獲取物件鎖。
3,synchronized(底層原理)
syncronized用的鎖存在java物件的對像頭裡。
3.1,什麼是物件頭?
①Java物件在堆中的基本的基本結構分三部分(有點像計算機網路中報文或者幀的結構,字首+資料+字尾):header(對相頭)+例項資料+對齊填充。
②物件頭,非陣列物件頭為兩個字寬(32位一字寬位332bit,64位為64bit)為Mark Word(標識位,標識鎖狀態等資訊,)部分和,Class Metadata Address
(儲存到物件型別資料的指標),如果是陣列物件用三個字寬,加上陣列長度Array length。
③Mark Word的儲存結構:
(鎖輕量,重量,偏向指的是synchronized鎖升級後引入的),物件頭裡儲存鎖狀態資訊,鎖的型別,包括持有鎖的執行緒ID等資訊。
其中重量鎖(升級前只有重量鎖)狀態下,Mark Word中有指向物件關聯的Monitor(監視器鎖)的指標。偏向鎖狀態下,儲存有當前
持有該物件的執行緒ID.
3.2,什麼是Monitor?
①一種實現同步的工具,每個java物件都可以關聯一個Monitor物件,(每個java物件都可以是鎖。)
②是實現synchronized內建鎖的關鍵。
- Monitor物件如何與java物件關聯,
<1>Java物件被某執行緒獲取鎖,物件頭Mark Word中會儲存指向Monitor的指標。
<2>Monitor物件的Owner欄位會存放相關聯的物件的獲取其物件鎖的執行緒ID,
每個物件關聯一個Monitor鎖,Monitor鎖,有一個阻塞佇列,用於存放阻塞等待搶鎖執行緒
3.3,synchronized鎖
3.3.1synchronized是一種排他鎖(只能有一個執行緒持有鎖)也是一種可重入鎖(已持有鎖的執行緒再次獲得鎖。避免自己將自己阻塞,排他,不排自己)
優化之前(加鎖機制):
①如果monitor的進入數為0,則該執行緒進入monitor,然後將進入數設定為1,該執行緒即為monitor的owner
②如果執行緒已經佔有該monitor,只是重新進入,則進入monitor的進入數加1.
③如果其他執行緒已經佔用了monitor,則該執行緒進入阻塞狀態,直到monitor的進入數為0,再重新嘗試獲取monitor的所有權
優化之後:
引入偏向鎖,輕量級鎖,重量級鎖,自旋鎖。
優化和通過synchronized加鎖的過程是: 偏向鎖-->輕量級鎖-->重量級鎖
3.3.2偏向鎖:為了在無多執行緒競爭的情況下儘量減少不必要的輕量級鎖執行路徑
<1>即當某執行緒第一個獲得某物件鎖,設定為偏向鎖,Mark Word中標識為偏向鎖,並儲存當前偏向執行緒ID,
<2>當下一個執行緒來競爭鎖的時候,先比較偏向執行緒ID,如果是偏向執行緒,直接獲得鎖,不需要指行3.3.1中的②步驟,
對鎖標識位每增加1,就要有一個-1與之對應。如果不是偏向執行緒,則先CAS操作搶鎖,CAS成功(原偏向執行緒執行完或者中斷退出)
若CAS不成功,就產生競爭,準備撤銷偏向鎖。
<3>撤銷過程:(圖片參考(複製)自synchronized原理)
3.3.3,輕量級鎖:引入自旋鎖,減少競爭失敗立馬阻塞帶來的系統開銷
- 執行緒阻塞開銷:
java的執行緒是對映到作業系統原生執行緒之上的,如果要阻塞或喚醒一個執行緒就需要作業系統介入,
需要在戶態與核心態之間切換,這種切換會消耗大量的系統資源,因為使用者態與核心態都有各自專用的記憶體空間,
專用的暫存器等,使用者態切換至核心態需要傳遞給許多變數、引數給核心,核心也需要保護好使用者態在切換時的
一些暫存器值、變數等,以便核心態呼叫結束後切換回使用者態繼續工作。
- 但自旋(死迴圈,失敗了不立馬阻塞,接著搶一會,)同樣帶來CPU消耗,若以要規定自旋時長(重複搶鎖次數)到一定
還沒搶到,升級重量級鎖,在搶。
<1>執行緒通過CAS獲取鎖,成功-獲取鎖執行。不成功則有競爭,進入自旋搶鎖階段,自旋一定次數仍搶不到表示競爭激烈
升級為重量級鎖。
3.3.4,重量級鎖:執行緒競爭失敗會進入阻塞佇列,掛起阻塞等待喚醒。
3.3.5,優化總結:
①引入偏向鎖的目的:在只有單執行緒執行情況下,儘量減少不必要的輕量級鎖執行路徑,輕量級鎖的獲取及釋放依賴多次CAS原子指令,
而偏向鎖只依賴一次CAS原子指令置換ThreadID,之後只要判斷執行緒ID為當前執行緒即可,偏向鎖使用了一種等到競爭出現才釋放鎖的機制,
消除偏向鎖的開銷還是蠻大的。如果同步資源或程式碼一直都是多執行緒訪問的,那麼消除偏向鎖這一步驟對你來說就是多餘的,
可以通過-XX:-UseBiasedLocking=false來關閉
②引入輕量級鎖的目的:在多執行緒交替執行同步塊的情況下,儘量避免重量級鎖引起的效能消耗(使用者態和核心態轉換),但是如果多個
執行緒在同一時刻進入臨界區,會導致輕量級鎖膨脹升級重量級鎖,所以輕量級鎖的出現並非是要替代重量級鎖
③重入:對於不同級別的鎖都有重入策略,偏向鎖:單執行緒獨佔,重入只用檢查threadId等於該執行緒;輕量級鎖:重入將棧幀中lock record的header
設定為null,重入退出,只用彈出棧幀,直到最後一個重入退出CAS寫回資料釋放鎖;重量級鎖:重入_recursions++,重入退出_recursions--,_recursions=0時釋放鎖
3.4,synchronized鎖,等待/喚醒機制:
3.4.1,進入重量級鎖阻塞佇列的執行緒,需要外界來喚醒,
Object類中:
wait() 呼叫該物件的執行緒,進入WATING狀態,只能等待另外執行緒喚醒。呼叫wait()後會釋放物件鎖。
wait(long) 超時喚醒,若在設定時間(毫秒)內沒有被其他執行緒喚醒,則自動喚醒位RUNNABLE狀態,競爭搶鎖。
wait(long,int) 時間單位更精確(可達到納秒)
notify() 通知等待佇列的一個執行緒,使其從wait()方法中返回,返回的前提是獲取到物件鎖
notifyAll() 通知所有該物件等待佇列執行緒。
3.4.2,wait(),和sleep()的區別:
-
-
- wait() 是 Object 的方法,而 sleep() 是 Thread 的靜態方法;
- wait() 會釋放鎖,sleep() 不會
-
參考: