synchronized關鍵字用於解決多個線程之間訪問資源的同步性,synchronized關鍵字能夠保證被它修飾的方法或者代碼塊在任意時刻只能有一個線程執行。java
值得注意的是,在Java早期,JDK1.6以前,synchronized屬於重量級鎖,效率低下。git
緣由在於:編程
監視器鎖【monitor】依賴於底層操做系統的
Mutex Lock
實現,Java的線程是映射到操做系統的原生線程之上的。若是要掛起或喚醒一個線程,都須要操做系統幫忙完成,而操做系統實現線程之間的切換時須要從用戶態轉化到內核態,須要消耗比較長的時間。數組
可是,JDK1.6以後,Java官方從JVM層面對synchronized關鍵字進行了較大的優化,效率不可同日而語。主要的優化有:自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減小鎖操做的開銷。安全
注意:靜態成員不屬於任何一個實例對象,是類成員!所以,一個線程A調用一個實例對象的非靜態synchronized方法,一個線程B調用這個實例對象的所屬類的靜態synchronized方法,是被容許的。多線程
由於訪問靜態
synchronized
方法佔用的鎖是當前類的鎖,而訪問非靜態synchronized
方法佔用的鎖是當前實例對象鎖。併發
synchronized(this|object)
表示進入同步代碼庫前要得到給定對象的鎖。synchronized(類.class)
表示進入同步代碼前要得到 當前 class 的鎖。經過對.class文件反編譯能夠發現:ide
ACC_SYNCHRONIZED
修飾。monitorenter
和monitorexit
兩個指令實現。雖然二者實現細節不一樣,但其實本質上都是JVM基於進入和退出Monitor對象來實現同步,JVM的要求以下:性能
monitorenter
指令會在編譯後插入到同步代碼塊的開始位置,而monitorexit
則會插入到方法結束和異常處。monitor
與之關聯,且當一個monitor
被持有以後,他會處於鎖定狀態。monitorenter
時,會嘗試獲取對象對應monitor
的全部權。monitorexit
時,鎖計數器減一,計數爲零則鎖釋放。https://blog.csdn.net/qq_34337272/article/details/108498442測試
優化:偏向鎖,輕量級鎖,自旋鎖,適應性自旋鎖,鎖消除,鎖粗化。
鎖主要存在的四種狀態,依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態,他們會隨着競爭的激烈而逐漸升級。注意鎖能夠升級不可降級,這種策略是爲了提升得到鎖和釋放鎖的效率。
鎖存在於Java對象頭裏,對象頭的組成部分:
Java對象頭又存在於Java堆中,堆內存分爲三部分:對象頭,實例數據和對齊填充。
Java對象頭的MardWord中記錄了對象和鎖的相關信息,無鎖狀態下,Java對象頭裏的Mark Word裏默認存儲對象的HashCode、分代年齡和鎖標記位。在64位的JVM中,Mark Word爲64 bit。
在運行期間Mark Word裏存儲的數據會隨着鎖標誌位的變化而變化。鎖升級的功能也主要靠MarkWord中鎖標誌位和是否偏向鎖標誌完成。
鎖升級的過程:無鎖,偏向鎖,輕量級鎖,重量級鎖
HotSpot的做者通過研究發現,大多數狀況下,鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,爲了讓線程得到鎖的代價更低而引入了偏向鎖。
偏向鎖主要用於優化:同一線程屢次申請同一個鎖的競爭,在某些狀況下,大部分時間都是同一個線程競爭鎖資源的。
主要流程:當一個線程訪問同步塊並獲取鎖時,會在對象頭和棧幀中的鎖記錄裏存儲鎖偏向的線程ID,之後該線程在進入和退出同步塊時不須要進行CAS操做來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word裏是否存儲着指向當前線程的偏向鎖。
一旦出現其餘線程競爭鎖資源時,偏向鎖就會被撤銷。偏向鎖的撤銷可能須要等待全局安全點【在這個時間點上沒有正在執行的字節碼】。
偏向鎖在Java 6和Java 7裏是默認啓用的,可是它在應用程序啓動幾秒鐘以後才激活,若有必要可使用JVM參數來關閉延遲:-XX:BiasedLockingStartupDelay=0
。
若是鎖一般處於競爭狀態,能夠經過- XX:-UseBiasedLocking=false
,進入輕量級鎖狀態。
如偏向鎖存在,若有另外一線程競爭鎖,且對象頭MarkWord中的線程ID與當前線程ID不一樣,則該線程將會嘗試CAS操做獲取鎖,獲取失敗,表明鎖存在競爭,偏向鎖向輕量級鎖升級。
線程交替執行同步塊,絕大部分的鎖在整個同步週期內都不存在長時間的競爭。
鎖 | 優勢 | 缺點 | 適用場景 |
---|---|---|---|
偏向鎖 | 加鎖和解鎖不須要額外的消耗,和執行非同步方法比僅存在納秒級的差距。 | 若是線程間存在鎖競爭, 會帶來額外的鎖撤銷的消耗。 |
適用於只有一個線程訪問同步塊場景。 |
輕量級鎖 | 競爭的線程不會阻塞,提升了程序的響應速度。 | 若是始終得不到鎖競爭的線程使用自旋會消耗CPU。 | 追求響應時間。同步塊執行速度很是快。 |
重量級鎖 | 線程競爭不使用自旋,不會消耗CPU。 | 線程阻塞,響應時間緩慢。 | 追求吞吐量。同步塊執行速度較長。 |
synchronized依賴於JVM實現,ReentrantLock依賴於API。
相比synchronized,ReentrantLock增長了一些高級功能。主要來講主要有三點:
ReentrantLock
提供了一種可以中斷等待鎖的線程的機制,經過 lock.lockInterruptibly()
來實現這個機制。也就是說正在等待的線程能夠選擇放棄等待,改成處理其餘事情。ReentrantLock
能夠指定是公平鎖仍是非公平鎖。而synchronized
只能是非公平鎖。所謂的公平鎖就是先等待的線程先得到鎖。ReentrantLock
默認狀況是非公平的,能夠經過 ReentrantLock
類的ReentrantLock(boolean fair)
構造方法來制定是不是公平的。synchronized
關鍵字與wait()
和notify()
/notifyAll()
方法相結合能夠實現等待/通知機制。ReentrantLock
類固然也能夠實現,可是須要藉助於Condition
接口與newCondition()
方法。