深入淺出 Java Concurrency (15): 鎖機制 part 10 鎖的一些其它問題

來源:互聯網
上載者:User

標籤:pos   避免   程式   線程阻塞   str   支援   同步   避免死結   java語言   

 

 

主要談談鎖的效能以及其它一些理論知識,內容主要的出處是《Java Concurrency in Practice》,結合自己的理解和實際應用對鎖機制進行一個小小的總結。

 

首先需要強調的一點是:所有鎖(包括內建鎖和進階鎖)都是有效能消耗的,也就是說在高並發的情況下,由於鎖機制帶來的環境切換、資源同步等消耗是非常可觀的。在某些極端情況下,線程在鎖上的消耗可能比線程本身的消耗還要多。所以如果可能的話,在任何情況下都盡量少用鎖,如果不可避免那麼採用非阻塞演算法是一個不錯的解決方案,但是卻也不是絕對的。

 

內部鎖

Java語言通過synchronized關鍵字來保證原子性。這是因為每一個Object都有一個隱含的鎖,這個也稱作監視器對象。在進入synchronized之前自動擷取此內部鎖,而一旦離開此方式(不管通過和中方式離開此方法)都會自動釋放鎖。顯然這是一個獨佔鎖,每個鎖請求之間是互斥的。相對於前面介紹的眾多進階鎖(Lock/ReadWriteLock等),synchronized的代價都比後者要高。但是synchronized的文法比較簡單,而且也比較容易使用和理解,不容易寫法上的錯誤。而我們知道Lock一旦調用了lock()方法擷取到鎖而未正確釋放的話很有可能就死結了。所以Lock的釋放操作總是跟在finally代碼塊裡面,這在代碼結構上也是一次調整和冗餘。另外前面介紹中說過Lock的實現已經將硬體資源用到了極致,所以未來可最佳化的空間不大,除非硬體有了更高的效能。但是synchronized只是規範的一種實現,這在不同的平台不同的硬體還有很高的提升空間,未來Java在鎖上的最佳化也會主要在這上面。

 

效能

由於鎖總是帶了效能影響,所以是否使用鎖和使用鎖的場合就變得尤為重要。如果在一個高並發的Web請求中使用了強制的獨佔鎖,那麼就可以發現Web的輸送量將急劇下降。

為了利用並發來提高效能,出發點就是:更有效利用現有的資源,同時讓程式儘可能的開拓更多可用的資源。這意味著機器儘可能的處於忙碌的狀態,通常意義是說CPU忙於計算,而不是等待。當然CPU要做有用的事情,而不是進行無謂的迴圈。當然在實踐中通常會預留一些資源出來以便應急特殊情況,這在以後的線程池並發中可以看到很多例子。

 

線程阻塞

鎖機制的實現通常需要作業系統提供支援,顯然這會增加開銷。當鎖競爭的時候,失敗的線程必然會發生阻塞。JVM既能自旋等待(不斷嘗試,知道成功,很多CAS就是這樣實現的),也能夠在作業系統中掛起阻塞的線程,直到逾時或者被喚醒。通常情況下這取決於環境切換的開銷以及與擷取鎖需要等待的時間二者之間的關係。自旋等待適合於比較短的等待,而掛起線程比較適合那些比較耗時的等待。

掛起一個線程可能是因為無法擷取到鎖,或者需要某個特定的條件,或者耗時的I/O操作。掛起一個線程需要兩次額外的環境切換以及作業系統、緩衝等多資源的配合:如果線程被提前換出,那麼一旦拿到鎖或者條件滿足,那麼又需要將線程換回執行隊列,這對線程而言,兩次環境切換可能比較耗時。

 

鎖競爭

影響鎖競爭性的條件有兩個:鎖被請求的頻率和每次持有鎖的時間。顯然當而這二者都很小的時候,鎖競爭不會成為主要的瓶頸。但是如果鎖使用不當,導致二者都比較大,那麼很有可能CPU不能有效處理任務,任務被大量堆積。

所以減少鎖競爭的方式有下面三種:

  1. 減少鎖持有的時間
  2. 減少鎖請求的頻率
  3. 採用共用鎖定取代獨佔鎖

 

死結

如果一個線程永遠不釋放另外一個線程需要的資源那麼就會導致死結。這有兩種情況:一種情況是線程A永遠不釋放鎖,結果B一直拿不到鎖,所以線程B就“死掉”了;第二種情況下,線程A擁有線程B需要的鎖Y,同時線程B擁有線程A需要的鎖X,那麼這時候線程A/B互相依賴對方釋放鎖,於是二者都“死掉”了。

還有一種情況為發生死結,如果一個線程總是不能被調度,那麼等待此線程結果的線程可能就死結了。這種情況叫做線程饑餓死結。比如說在前面介紹的非公平鎖中,如果某些線程非常活躍,在高並發情況下這類線程可能總是拿到鎖,那麼那些活躍度低的線程可能就一直拿不到鎖,這樣就發生了“饑餓死”。

避免死結的解決方案是:儘可能的按照鎖的使用規範請求鎖,另外鎖的請求粒度要小(不要在不需要鎖的地方佔用鎖,鎖不用了儘快釋放);在進階鎖裡面總是使用tryLock或者定時機制(這個以後會講,就是指定擷取鎖逾時的時間,如果時間到了還沒有擷取到鎖那麼就放棄)。進階鎖(Lock)裡面的這兩種方式可以有效避免死結。

 

活鎖

活鎖描述的是線程總是嘗試某項操作卻總是失敗的情況。這種情況下儘管線程沒有被阻塞,但是人物卻總是不能被執行。比如在一個死迴圈裡面總是嘗試做某件事,結果卻總是失敗,現線上程將永遠不能跳出這個迴圈。另外一種情況是在一個隊列中每次從隊列頭取出一個任務來執行,每次都失敗,然後將任務放入隊列頭,接下來再一次從隊列頭取出任務執行,仍然失敗。

還有一種活鎖方式發生在“碰撞協讓”情況下:兩個人過獨木橋,如果在半路相撞,雙方禮貌退出去然後再試一次。如果總是失敗,那麼這兩個任務將一直無法得到執行。

 

總之解決鎖問題的關鍵就是:從簡單的開始,先保證正確,然後再開始最佳化。

深入淺出 Java Concurrency (15): 鎖機制 part 10 鎖的一些其它問題

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.