JAVA鎖漫談,最好的鎖是無鎖

關於synchronizedjava

衆所周知,JAVA中最簡單的加鎖方法是用關鍵字synchronized,咱們可使用這個關鍵字將一個方法變成線程安全的,也能夠將一個代碼塊變成線程安全的,這樣子咱們不須要再擔憂多線程同時執行到這段代碼會引起的併發問題。同時配合方法wait,notify和notifyall能夠很好的實現多線程之間的協做,好比某個線程由於須要等待一些資源,因而調用wait方法將本身設置爲waiting狀態,其餘線程釋放或生產這個線程須要的資源的時候須要通知這個線程(notify)將其喚醒,或者通知全部等待當前資源的線程(notifyall)。
安全

然而當功能完成以後咱們彷佛並不知足於此,因而咱們開始考慮這麼作的代價是什麼,是否能夠作的更好。多線程

先說說這麼作(使用synchronized)的代價是什麼,當多個線程請求臨界資源的時候只能有一個線程獲得知足,那麼其餘的線程會作什麼呢,他們會被阻塞,直到被通知(notify/notifyall)又有資源的時候才被喚醒進行再一次的鎖爭用,然後往復的是又只有一個線程能被獲得知足,其餘的線程繼續進入阻塞狀態,而這個時候可能會有不斷的增長爭用線程。性能損耗的關鍵點在於線程的阻塞操做是由操做系統來完成的,在Linux系統下是由pthread_mutex_lock函數來完成。線程被阻塞以後便進入了內核調度態,這個過程發生了操做系統將保存用戶態的上下文進入內核態,這也就是常說的上下文切換,上下文切換代價大,在於操做系統須要將當前線程執行上下文內容(包括堆棧、寄存器等存儲的內容)的保存以便以後線程切換回來時候再進行現場恢復。併發

上面能夠看出使用synchronized的代價是什麼了吧,當競爭激烈的時候會引發頻繁的操做系統上下文切換,從而影響系統的性能。下面再來說講自旋鎖。框架


自旋鎖的原理函數

自旋鎖是對線程阻塞的一種優化,他的原理簡單的說就是當線程爭用鎖失敗的時候不當即進入阻塞狀態,而是再等一會,由於對於執行時間短的代碼這一會可能就會釋放鎖,而線程就不須要進行一次阻塞與喚醒。等待操做就是讓線程多執行幾個空指令,至於等待多久這跟具體的處理器實現有關,也有可能處理器根本不支持自旋鎖,具體實現的時候咱們能夠設置一個臨界值,當超過了這個臨界值以後咱們就不自旋了,就乖乖進入阻塞狀態吧。這種優化對於執行時間短的代碼是頗有效的。synchronized使用自旋鎖的時機是線程進入等待隊列即阻塞的前一步。性能


關於偏向鎖優化

偏向鎖是java6提供的一種功能,主要是對無競爭條件下的對加鎖代碼執行的優化,獲得優化的地方是省去了對等待隊列的更新操做。在競爭條件下,獲取鎖失敗的線程會被放入等待隊列,這個隊列的更新操做是經過CAS指令來完成的。對於那麼一段本部應該被加鎖的代碼被加了鎖,咱們認爲每次執行這段被加了鎖的代碼的時候更新等待隊列的操做並非必要的,而CAS操做會延遲本地代碼的執行,所以偏向鎖是用於優化這個問題的。操作系統


關於Lock線程

Lock是JAVA5增長的內容,在JCU(java.util.concurrent.locks)包下面,做者是併發大師Doug Lea。JCU包提供了不少封裝的鎖,包括經常使用的ReentrantLock和ReadWriteLock。這些所其實都是依賴java.util.concurrent.AbstractQueuedSynchronizer這個類來實現的,這個類有個簡寫的名字叫AQS,對這就是著名的AQS。

關於Lock,先說說線程獲取Lock鎖的時候會引發哪些事件呢。首先AQS是依賴一個被volatile修飾的int變量來標識當前鎖的狀態的,爲0的時候表明當前鎖不被任何線程擁有,當線程拿到這個鎖的時候會經過CAS操做修改state的狀態,那麼對於爭用失敗的線程AQS會怎麼辦呢,AQS內部維護了一個等待隊列,這個隊列是純JAVA實現的,其實現也是很是巧妙的,多線程在經過CAS來獲取本身在隊列中的位置,同時隊列中的線程狀態也是阻塞狀態,遇到阻塞就頭疼了,上面已經介紹過阻塞會帶來的性能問題。在源碼中咱們能夠看到的是AQS經過LockSupport(LockSupport底層依賴Unsafe)將線程阻塞,關於LockSupport我有一篇文章介紹的,其功能是用來代替wait和notity/notifyall的,更好的地方是LockSupport對park方法和unpark方法的調用沒有前後的限制,而notify/notifyall必須在wait調用以後調用。儘管如此,這一切並無阻止線程進入阻塞狀態,我有點失望。


無鎖時代

講到無鎖,必然是Disruptor併發框架,Disruptor底層依賴一個RingBuffer來進行線程之間的數據交換,無鎖在於在併發條件下,多線程對RingBuffer的讀和寫不會涉及到鎖,然而由於RingBuffer滿或者RingBuffer中沒有可消費內容引起的線程等待,那就要另當別論了。簡單幾句介紹下無鎖原理,RingBuffer維護者可讀和可寫的指針,也叫遊標,它指向生產者或消費者須要寫或讀的位置,而對於指針的更新是由CAS來完成的,這個過程當中咱們不須要加鎖/解鎖的過程。


後記:

JAVA鎖方面的知識主要是要搞清楚不一樣的鎖的優勢與缺點,深刻到操做系統層的實現機制與不一樣場景中對應用的性能影響。本文簡單的擼了一下JAVA鎖從synchronized到無鎖的發展以及一些鎖的簡單原理,主要是拋磚引玉吧,由於介紹的比較簡單,對於文中提到的知識不知道的同窗能夠深刻了解,我相信你會頗有收穫。有些實現的原理介紹可能就一句話,可是實際實現起來是蠻複雜的,須要考慮到的東西是咱們沒有寫過所不能考慮到的。到這裏,若是你的項目中用到了多線程併發,你是否會考慮使用無鎖模型來優化你項目中多線程之間的通訊呢。


本文由做者原創,轉載需著名來源,做者熱愛JAVA技術,但願多多交流。QQ:378979705

相關文章
相關標籤/搜索