Synchronized在JDK1 6中的優化

2021-10-07 16:11:26 字數 3119 閱讀 8124

首先什麼是自旋鎖?有時候執行緒去競爭鎖失敗,進入了阻塞狀態,但剛剛進入阻塞狀態後持有鎖的執行緒就釋放了鎖資源,這個時候執行緒又會被喚醒繼續執行,考慮到這種情況,jdk1.4中引入了自旋鎖的概念,就是在sychronized同步**塊中,如果執行緒沒有競爭到鎖,則讓它先進行一段無意義的自旋,避免執行緒直接進入阻塞狀態,自旋結束後如果持有鎖的執行緒已釋放鎖,則當前執行緒可直接開始執行。顯然,這樣處理會有個壞處,如果每一次執行緒未競爭到鎖都進行一段時間的自旋,且每一次自旋結束後都依然沒有獲取到鎖,那麼自旋的操作就白白浪費了cpu的效能。在jdk1.4引入自旋鎖的時候我們可以通過jvm啟動引數-xx:+usespinning開啟自旋鎖,使用-xx:preblockspin設定自旋鎖的等待次數,而jdk1.6引入適應性自旋鎖後,自旋鎖機制變得更加"聰明",它可以動態的調整每一次自旋的時間,調整的依據就是上乙個獲取到鎖的執行緒獲取鎖用了多少時間,如果用時較短,它就認為這個鎖比較容易獲取,那就適當的延長自旋的時間以等待這個鎖的釋放,如果用時較長,則縮短當前執行緒自旋的時間或者不自旋直接進入阻塞狀態,以避免長時間自旋後依然無法獲取鎖的情況。

鎖消除簡單來說,就是在不可能出現鎖資源的競爭,不需要加鎖的情況下,如果我們使用了synchronized鎖,則jvm會在編譯的時候直接將這個鎖消除,以減少鎖的操作,提公升**的執行效率。jvm判斷這個方法是否執行緒安全,是否不可能出現鎖資源競爭的時候,會進行逃逸分析,所以鎖消除有兩個前提,1.該程式必須以server模式執行 2.必須開啟逃逸分析和鎖消除(通過jvm啟動引數設定,-xx:+doescapeanalysis表示開啟逃逸分析,-xx:+eliminatelocks表示鎖消除)

一般在加鎖的時候,都會讓鎖的粒度足夠小,盡量在synchronized同步**塊中只包含真正需要加鎖的**,以減少鎖占用的時間,提高**執行效能。但有時候,一味的減小鎖的粒度,會增加競爭鎖的次數,同乙個執行緒會頻繁獲取同乙個鎖,出現這種情況時,sychronized會自動幫我們提公升鎖的粒度,減少競爭鎖的次數,如下,以極端情況舉例:

鎖粗化前:

public void test()}}

鎖粗化後:

public void test() }}

鎖的分類jdk1.6之後,synchronized有偏向鎖、輕量級鎖、重量級鎖三種鎖機制。synchronized有乙個特點,就是使用synchronized同步**塊或synchronized方法時,所有的物件都可以作為鎖物件,用synchronized修飾的方法,鎖物件為當前類的例項物件;synchronized修飾的靜態方法,鎖物件為當前類的class物件。那既然所有物件都能作為synchronized的鎖,那儲存物件的鎖資訊(當前物件的鎖狀態,被哪個執行緒持有鎖,有哪些執行緒在等待鎖等)最好的方式就是將其儲存在物件的物件頭中。

物件頭

在hotspot虛擬機器中,物件在記憶體中的分布可分為三個部分:1.物件頭 2.例項資訊 3.資料填充。其中物件頭又分為mark word和型別指標兩部分,mark word用於儲存物件自身的執行時資料,如:gc分代年齡、hashcode、陣列長度(如果該物件為陣列)、鎖狀態標誌、是否偏向、偏向執行緒id等等。型別指標部分則儲存該物件指向其類的元資料的指標。

mark word的儲存特點

在32位和64位的系統中,物件頭的mark word分別占用32個和64個bitmap,為了盡可能多的儲存物件執行時的資料,mark word被設計成了可根據鎖狀態動態分配各部分儲存空間大小的資料結構,見下圖

幾種鎖的狀態

1.無鎖狀態

當物件為無鎖狀態時,mark word主要儲存物件的hashcode、分代年齡等資訊,如果物件為陣列型別,則還會儲存該陣列的長度。這就是為什麼通過類的元資料可以直接獲取物件的大小,但通過類的元資料不能獲取陣列的長度。

2.偏向鎖

當執行緒進入synchronized同步**塊或同步方法時,如果物件鎖的物件頭中儲存的偏向鎖線程id為0,則該物件會在mark word中以cas的方式儲存當前執行緒的執行緒id。如果有另乙個執行緒id來競爭這個物件鎖,則公升級為輕量級鎖

適用場景:很多函式(特別是第三方包提供的)雖然在設計時考慮了併發的情況,做了執行緒安全的控制,但在實際使用場景中,或許根本就只有一條執行緒在呼叫,這種情況就可以使用偏向鎖,將使用這個鎖的執行緒id記錄,避免同一條執行緒頻繁的競爭和釋放同乙個鎖而帶來效能開銷。而一旦出現另一條執行緒來獲取鎖,說明這個鎖物件是存在鎖資源競爭的,則將該物件的鎖狀態公升級為輕量鎖。

3.輕量級鎖當物件的鎖狀態為輕量級鎖時,markword區記錄的是「指向棧中鎖記錄的指標」,這個指標指向的是當前持有鎖的執行緒的棧空間中的乙個棧幀,這個棧幀叫做lock record,如下圖所示,lock record分為兩部分,一部分複製儲存鎖物件的mark word,一部分儲存鎖物件的位址。

當執行緒第一次進入同步**塊時,會在當前執行緒的棧空間中生成乙個lock record,將鎖物件的mark word區儲存的物件頭資訊儲存下來,並且記錄鎖物件的位址,然後通過cas的方式將lock record的位址記錄到鎖物件的markword,解鎖時也是通過cas的方式將lock record中記錄的鎖物件的hashcode、分代年齡等資訊記錄到鎖物件的markword區,並將鎖標誌改為無鎖狀態。無論加鎖還是解鎖的cas操作如果操作失敗,則將鎖公升級為重量級鎖。

適用場景:很多情況下,我們會盡量讓鎖的粒度足夠小,以較少鎖占用的時間,所以在併發量不大時,同步塊中的**其實是各個執行緒交替執行而並不會發生鎖競爭的,這種時候就應該使用輕量級鎖,加鎖和解鎖都使用cas操作,不會涉及執行緒上下文的切換、使用者態和核心態的切換。而出現鎖競爭的情況時,就公升級為重量級鎖。

總結:偏向鎖適用於只有一條執行緒訪問同步塊,輕量級鎖適用於多條執行緒交替訪問同步塊,重量級鎖適用於多條執行緒同時訪問同步塊。

Synchronized在多執行緒中的使用

同步多執行緒 當兩個併發執行緒訪問同乙個物件object中的synchronized this 同步 塊時,一段時間內只能有乙個執行緒被執行,另乙個必須等待當前執行緒執行完這個 塊以後才能執行改 class task system.out.println nend task class thread...

JD實習面經(二)

先自我介紹下 你能用通俗的語言來介紹下jvm裡面的堆嗎 站在我是乙個不懂計算機的角度來介紹 jvm裡面會發生記憶體溢位的區域 介紹下記憶體溢位,有什麼辦法避免記憶體溢位?給我介紹下堆排序。mysql的儲存引擎介紹。你知道為什麼使用b 樹做索引嗎?介紹一下執行緒池主要是執行緒池的引數介紹。介紹些執行緒...

synchronized在多執行緒情況下的使用

不同業務場景,有時會碰到大量資料的情況,在請求完資料後會通過model對映到對應的陣列或者字典中,從而對陣列進行操作,而多個執行緒同時對同一陣列進行取捨時內容就會出錯,為了避免這種情況可以使用 synchronized關鍵字來宣告來建立乙個互斥鎖,保證此時沒有其它執行緒對鎖定物件進行修改 synch...