線程安全與鎖優(yōu)化

什么是線程安全

過往在使用synchronized關(guān)鍵字的時候廊遍,通常都會和線程安全問題相掛鉤。那么這個線程安全的定義又是什么呢祟滴? 在我學(xué)習《深入JVM虛擬機》這本書中提到了一段話我覺得解釋的不錯: "當多個線程訪問一個對象是刊懈,如果不用考慮這些線程在運行時環(huán)境下的的調(diào)度和交替執(zhí)行,也不需要進行額外的同步捷凄,或者在調(diào)用方進行任何其他的協(xié)調(diào)操作杠览,調(diào)用這對象的行為都可以獲得正確的結(jié)果,那這個對象就是線程安全的 " 這個定義是比較嚴謹?shù)淖菔疲瑥乃拿枋鰜砜淳€程安全必須具備的一個特征就是踱阿; 一段代碼本身封裝了所有必要的正確性保障手段(如互斥同步等), 另調(diào)用者無需關(guān)心多線程的安全問題管钳,更無須自己采取任何措施來保證多線程的正確調(diào)用。


Java語言中的線程安全分類

為了更好的理解線程安全软舌, 我們將線程安全性分解為更多的層次才漆, 將線程安全程度有高到低進行排列: 不可變 -> 絕對線程安全 -> 相對線程安全 -> 線程兼容 -> 線程對立

  • 不可變(Immutable)

    在Java 語言中, 不可變對象一定是線程安全的(例如String), 無論是對象的方法實現(xiàn)還是方法的調(diào)用者佛点,都不需要采取任何措施來保障線程安全性問題醇滥,在java中實現(xiàn)不可變對象通常用final修飾, 一旦一個不可變對象被正確構(gòu)建出來(沒有發(fā)生引用逃逸問題超营, 具體可參考final關(guān)鍵字的內(nèi)存可見性)鸳玩, 那么對外部的可見狀態(tài)就永遠不會改變,多線程之中就永遠處于一致狀態(tài)演闭,此種安全性也是最簡單和最純粹的不跟。

  • 絕對線程安全

    絕對安全的定義是滿足所有之前線程安全的定義的, 但是要做到這點基本上是非趁着觯苛刻的窝革。 Java中的大部分API大部分標榜了線程安全的類基本都不是絕對線程安全的,如果操作不當吕座,還是會出現(xiàn)多線程同時讀寫情況下出現(xiàn)數(shù)據(jù)安全問題虐译。比如Vector類在多線程讀和remove的情況下, 可能就會出現(xiàn)數(shù)組越界的異常吴趴。 以及可以參考Collections.SynchronizedList的迭代器(Iterator)漆诽, 該類原作者還特意標明了該方法需要 Must be manually synched by user (用戶手動同步)。此類方法并不符合調(diào)用方任意操作都能獲取正確結(jié)果的線程安全定義锣枝。

  • 相對線程安全

    相對線程安全就是我們通常定義的線程安全拴泌,需要保證對這個對象單獨操作是線程安全的,我們在調(diào)用的時候不需要做額外的保護措施惊橱,但是對于一些特定順序的連續(xù)調(diào)用蚪腐,就可能需要在調(diào)用段使用額外的同步手段來保證調(diào)用的正確性。Java中比較常見的線程安全類型基本都屬于這種類型税朴,例如上面舉例的Vector回季,HashTable,以及Collections.synchronized集合方法包裝的集合類正林。

  • 線程兼容

    線程兼容通常是對象本身并不具備線程安全性泡一,但是通常可以通過一些同步手段來實現(xiàn)線程安全觅廓。我們平常所說的線程非安全鼻忠,基本都是在說著一種情況。Java API絕大多數(shù)類都是線程兼容的例如 ArrayList杈绸,HashMap

  • 線程對立

    線程對立是指無論調(diào)用段是否采取了同步措施帖蔓, 都無法在多線程環(huán)境中并發(fā)使用的代碼矮瘟,這種代碼通常都是有害的,應(yīng)當盡量避免塑娇。

    最典型的例子就是Thread類的suspend()和resume()方法澈侠,如果有兩個線程同時持有線程對象,一個嘗試去終端中斷線程埋酬,一個嘗試去恢復(fù)線程哨啃,如果是并發(fā)進行的話,無論調(diào)用時是否進行了線程同步写妥,目標線程都是存在死鎖風險的拳球。也正是這個原因jdk 廢棄了這兩個方法。


線程安全的實現(xiàn)方法
  • 互斥同步(悲觀鎖實現(xiàn))

    Synchronized是在學(xué)習Java時候最常見的一種線程安全保障手段珍特,基本原理就是在多個線程并發(fā)訪問共享數(shù)據(jù)是祝峻,保證共享數(shù)據(jù)在同一個時刻只能被一個線程鎖占用。 Synchronized在經(jīng)過編譯之后會形成monitorenter和monitorexit兩個字節(jié)指令次坡。根據(jù)虛擬機規(guī)范的要求,在執(zhí)行monitorenter的時候首先嘗試獲取對象的鎖画畅, 如果這個對象沒有被鎖定砸琅,或者當前線程已經(jīng)獲取到該對象的鎖(鎖重入),把鎖的計數(shù)器加1轴踱,相應(yīng)的momitorexit指令會將鎖計數(shù)器減1症脂,當計數(shù)器為0是,鎖被釋放淫僻。 除此之外诱篷,還有java.util.concurrent包下的ReentrantLock也是互斥鎖的一種實現(xiàn),區(qū)別在于一種是java語義層面的實現(xiàn)雳灵,一種是jvm級別的實現(xiàn)棕所。

  • 非阻塞同步(樂觀鎖實現(xiàn))

    互斥同步雖然實現(xiàn)了保障線程安全問題,但是在線程阻塞和喚醒的同時也帶來了性能問題悯辙,因此也稱為阻塞同步琳省。隨著指令集的發(fā)展,基于CAS(Compare-and-Swap)實現(xiàn)的非阻塞同步出現(xiàn)了躲撰。 基本思想就是先進行操作针贬,如果沒有其他線程爭搶共享數(shù)據(jù),那么操作就成功了拢蛋。如果產(chǎn)生了沖突桦他,則可以通過補償機制(類似重試, 直到成功),這種同步措施并不需要當前線程掛起谆棱。Java中比較常見的就是java.util.concurrent包下的一些Atomic* 類(原子類)快压。不過圆仔,盡管CAS實現(xiàn)了非阻塞的同步,但是也帶來了一些 比如ABA問題和大量線程空轉(zhuǎn)導(dǎo)致的cpu資源浪費等問題嗓节。

  • 非同步方案

    要保證線程安全荧缘,并不是一定要進行線程同步,同步只是保證在共享數(shù)據(jù)爭用時數(shù)據(jù)的正確性拦宣,但是如果一個方法本身就不涉及共享數(shù)據(jù)截粗,那么他本身就是線程安全的,就沒有必要對其進行同步鸵隧。常見的兩類如下:

Synchronized 優(yōu)化點

jdk1.6之后HotSpot虛擬機開發(fā)團隊話費了大量精力去實現(xiàn)各種鎖優(yōu)化技術(shù)绸罗,如適應(yīng)性自旋鎖,鎖消除豆瘫,鎖粗化珊蟀,輕量級鎖和偏向鎖等。

  • 自旋鎖和適應(yīng)性自旋鎖

    自旋鎖的實現(xiàn)可以參考前面非阻塞同步的內(nèi)容外驱,當有兩個或以上的線程同時爭搶一個共享數(shù)據(jù)的時候育灸, 我們可以讓后面請求鎖的線程不放棄CPU執(zhí)行時間,通過一種忙循環(huán)的方式實現(xiàn)去等待獲取鎖昵宇。但是我們也知道磅崭,這樣的方式勢必會造成CPU的資源浪費。因此通常自旋的線程等待一定時間后如果還沒有獲取到鎖瓦哎,那么就會用傳統(tǒng)的方式將線程掛起砸喻。 jdk1.6之后還引入了自適應(yīng)的自旋鎖,區(qū)別在與自適應(yīng)的自旋鎖自旋的時間不再是固定的了蒋譬,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態(tài)來定割岛。如果一個鎖對象上,自旋等待剛剛成功獲得鎖犯助,進而會將允許自旋等待持續(xù)相對更長的時間癣漆。 如果對于某個鎖,自旋很少成功獲得過剂买,拿在以后要獲取這個鎖是將可能省略掉自旋的過程扑媚,直接掛起,避免CPU資源的浪費雷恃。

    • 注: 自旋鎖主要還是在"重量級"鎖的場景下疆股,通過自旋的方式來減少通常線程掛起導(dǎo)致的性能損耗。
  • 鎖消除

    鎖消除是指虛擬機在運行時倒槐,對一些代碼上要求同步旬痹,但是被檢測到不可能存在共享數(shù)據(jù)競爭的環(huán)境,此時就會消除原有代碼上標明的同步機制,這個操作叫鎖消除两残。鎖消除的判定依據(jù)主要來源于逃逸分析(可參考占小狼的 淺談HotSpot逃逸分析) 永毅。

  • 鎖粗化

    原則上來說,我們編碼的時候人弓,總是推薦將同步代碼塊的作用范圍限制得盡量小沼死。但是如果一系列的連續(xù)操作都是對同一個對象反復(fù)進行加鎖和解鎖,甚至加鎖操作是在循環(huán)體中崔赌,那即使沒有線程競爭意蛀,頻繁進行互斥同步操作也會導(dǎo)致不必要的性能損耗。此時虛擬機就會把枷鎖同步的范圍擴展(粗化)到整個操作序列的外部健芭,減少反復(fù)加鎖的性能損耗县钥。

  • 偏向鎖和輕量級鎖

    關(guān)于偏向鎖和輕量級鎖以及鎖膨脹過程,我們在下一個篇幅繼續(xù)說明慈迈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末若贮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子痒留,更是在濱河造成了極大的恐慌谴麦,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伸头,死亡現(xiàn)場離奇詭異匾效,居然都是意外死亡,警方通過查閱死者的電腦和手機熊锭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門弧轧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雪侥,“玉大人碗殷,你說我怎么就攤上這事∷儆В” “怎么了锌妻?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長旬牲。 經(jīng)常有香客問我仿粹,道長,這世上最難降的妖魔是什么原茅? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任吭历,我火速辦了婚禮,結(jié)果婚禮上投储,老公的妹妹穿的比我還像新娘绍豁。我一直安慰自己沉唠,他們只是感情好焊夸,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布朗若。 她就那樣靜靜地躺著恼五,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哭懈。 梳的紋絲不亂的頭發(fā)上灾馒,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音遣总,去河邊找鬼睬罗。 笑死,一個胖子當著我的面吹牛彤避,可吹牛的內(nèi)容都是我干的傅物。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼琉预,長吁一口氣:“原來是場噩夢啊……” “哼董饰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起圆米,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤卒暂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后娄帖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體也祠,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年近速,在試婚紗的時候發(fā)現(xiàn)自己被綠了诈嘿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡削葱,死狀恐怖奖亚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情析砸,我是刑警寧澤昔字,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站首繁,受9級特大地震影響作郭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜弦疮,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一夹攒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧胁塞,春花似錦咏尝、人聲如沸堂湖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽无蜂。三九已至,卻和暖如春蒙谓,著一層夾襖步出監(jiān)牢的瞬間斥季,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工累驮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酣倾,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓谤专,卻偏偏與公主長得像躁锡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子置侍,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348