很久就想寫鎖這個主題了,但是一直不敢寫,因爲自己感覺我的能力還是不足以駕馭鎖,沒法覆蓋鎖的內容,後面一想我就先以我粗淺的知識說說自己心中的鎖,以後有更多的感悟再加上(當時面試阿里,面試官第一個問題就是說說對鎖的認識,不限於語言,不限於軟硬件,把你自己知道的說出來,現在回顧當時的回答是多麼的2 young 2 simple。哈哈^_^,後來一聊,人家是專門寫jvm gc的)。
我現在就給鎖下一個定義:
鎖:用來保護共享數據,實現原子性的一種機制。一方面軟件上根據不同的實現和不同的場景分了多種鎖;另一方面硬件提供了一些具有線性一致性的指令,比如atomic read-write,atomic swap,test-and-set,操作系統基於這些指令建立mutual exclusion、 semaphores 等鎖結構。
架構設計上的鎖:單機版,分佈式版。
根據不同場景下鎖的分類:
競爭激烈方面:悲觀鎖,樂觀鎖。具體來說,假定鎖競爭激烈,處於數據安全考慮,使用悲觀鎖;假定鎖競爭不激烈,使用樂觀鎖。
根據鎖住同步資源失敗後,線程是否要阻塞:如果阻塞就是內置鎖,如果不阻塞就是自旋鎖。
根據多個線程競爭同步資源時具體流程差異:
1不用鎖資源,多個線程中只有一個能訪問資源,其他線程會重試。該類稱爲無鎖。
2同一個線程執行同步資源時自動獲取資源。該類稱爲偏向鎖。
3多個線程競爭同步資源時,沒有獲取到資源的線程會自旋等待鎖釋放。該類稱爲輕量級鎖。
4當有多個線程競爭同步資源時,沒有資源的線程阻塞等待喚醒。該類稱爲重量級鎖。
根據判斷多個線程競爭鎖時要不要排隊:如果多個線程間排隊,該類稱爲公平鎖。如果多個線程可先嚐試插隊,插隊失敗後再排隊,該類稱爲非公平鎖。
根據判斷一個線程是否可以重複獲取同一個鎖:如果可以獲取同一個鎖,該類稱爲可重入鎖,如果不能獲取同一個鎖,該類稱爲非重入鎖。
根據判斷多個線程間是否可以共享一個鎖:如果可以共享,該類稱爲共享鎖,如果不能,該類稱爲排它鎖。
根據判斷server實例是單例還是多例,多個線程競爭同步資源:如果是單例就是上面我們說的所有情況下的鎖,如果是多例,則是分佈式鎖。
爲什麼現在的鎖會如此錯綜複雜,我認爲一方面是技術實現不同,二是關注的對象不同,上面根據不同的場景下對鎖進行了簡單的歸類,實際上還有些是不包括的,比如讀寫鎖,它的讀鎖時共享的,但是寫鎖是不共享的。
下面我就聊聊各種鎖吧:
1樂觀鎖VS悲觀鎖。
悲觀鎖:
針對多個線程競爭同步資源的操作,悲觀鎖認爲自己在使用數據的時候一定有別的線程來修改數據,因此在獲取數據的時候會先加鎖,確保數據不會被別的線程修改,現實社會中就是那種寧可我負別人,別人不能負我的那種。


樂觀鎖:
針對多個線程競爭同步資源的操作,樂觀鎖認爲自己在使用數據時不會有別的線程修改數據,所以不會添加鎖,只是在更新數據的時候去判斷之前有沒有別的線程更新了這個數據。如果這個數據沒有被更新,當前線程將自己修改的數據成功寫入。如果數據已經被其他線程更新,則根據不同的實現方式執行不同的操作(例如報錯或者自動重試),在現實社會中就是今朝有酒今朝醉的那種人。
實現樂觀鎖,悲觀鎖可查看我的另一篇博客(經過我考慮後,我把這篇博客作爲總綱,然後圍繞着鎖這個概念具體分析鎖的實現,由於鎖的實現篇幅太長,技術點也很多,把內容填充在這裏會顯得臃腫),博客地址:
todo
實現樂觀鎖主要包含這兩個步驟:衝突檢測和數據更新。樂觀鎖在Java中是通過使用無鎖編程來實現,最常採用的是CAS算法,CAS 操作包含三個操作數 —— 內存位置(V)、預期原值(A)和新值(B),它的核心思想是通過比對內存值與預期值。簡單來說cas操作可以減少加鎖帶來的開銷,但是cas也是有缺陷的,如果多個線程間經常鎖競爭,導致其一直自旋,帶來資源浪費,另一方面會產生ABA問題,下面給出cas的缺陷具體描述文章鏈接。
todo

鎖的實現:
用悲觀鎖概念實現的鎖:synchronized關鍵字和Lock的實現類。
用樂觀鎖概念實現的鎖:採用cas算法的實現類。
該分類下鎖的適應場景:
悲觀鎖適合寫操作多的場景,先加鎖可以保證寫操作時數據正確。
樂觀鎖適合讀操作多的場景,不加鎖的特點能夠使其讀操作的性能大幅提升。

2. 自旋鎖 VS 適應性自旋鎖
自旋鎖:
自旋鎖的實現原理爲CAS,AtomicInteger中調用unsafe進行自增操作的源碼中的do-while循環就是一個自旋操作,如果修改數值失敗則通過循環來執行自旋,直至修改成功。
 適應性自旋鎖:
自適應意味着自旋的時間(次數)不再固定,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,並且持有鎖的線程正在運行中,那麼虛擬機就會認爲這次自旋也是很有可能再次成功,進而它將允許自旋等待持續相對更長的時間。如果對於某個鎖,自旋很少成功獲得過,那在以後嘗試獲取這個鎖時將可能省略掉自旋過程,直接阻塞線程,避免浪費處理器資源。
對於自旋鎖的概念和自旋鎖的實現請查看以下鏈接:
todo
3. 無鎖 VS 偏向鎖 VS 輕量級鎖 VS 重量級鎖
這四種鎖是指鎖的狀態,專門針對synchronized的。
無鎖在多個線程獲取同步資源鎖失敗概率低的時候性能是最好的。
在大多數情況下,鎖總是由同一線程多次獲得,不存在多線程競爭,所以出現了偏向鎖。
偏向鎖通過對比Mark Word解決加鎖問題,CAS操作只執行一次。而輕量級鎖是通過用CAS操作和自旋來解決加鎖問題,避免線程阻塞和喚醒而影響性能。重量級鎖是將除了擁有鎖的線程以外的線程都阻塞。
4. 公平鎖 VS 非公平鎖
5. 可重入鎖 VS 非可重入鎖
6. 排他鎖(互斥鎖/獨享鎖) VS 共享鎖
什麼叫排他鎖:指該鎖一次只能被一個線程所持有,如果線程T對數據A加上排它鎖後,則其他線程不能再對A加任何類型的鎖,同時只有線程T對數據A有讀寫權限。
什麼叫共享鎖:指該鎖可被多個線程所持有,如果線程T對數據A加上共享鎖後,則其他線程只能對A再加共享鎖,不能加排它鎖,另一方面獲得共享鎖的線程只能讀數據,不能修改數據。
排他鎖與共享鎖的實現請查看以下鏈接:

7分佈式鎖

 

使用鎖所帶來的問題 
使用鎖之後可能帶來的問題有三種:死鎖,活鎖,飢餓鎖。
死鎖是兩個線程同時在請求對方佔有的資源;
活鎖是線程對任務的處理沒有取得任何進展;
飢餓是一個線程在無限地等待其他線程佔有的但是不會往外釋放的資源。
死鎖:
死鎖的解決辦法有:加鎖順序、加鎖時限、死鎖檢測,具體可參考:
http://ifeve.com/deadlock-prevention/
活鎖:線程對任務的處理絲毫取不到任何進展。
活鎖的解決辦法有:設置最大重試次數,超過該閾值後,監聽自動停止;消息監聽器監聽到消息後,若不成功,則存表,不作回滾處理,
    事後再進行定時異步補償處理;
飢餓:給線程設置了優先級,優先級低的線程始終得不到cpu資源沒有執行的機會。
飢餓的解決辦法有:在synchronized方法或者塊中避免無限循環、採用線程默認的優先級。

既然引入鎖有問題,那怎麼解決(優化)該問題呢?
鎖的優化
減小鎖粒度,鎖分離,鎖粗化,鎖消除

面試總結

1爲啥要把aqs設計成模板方法模式?

2讀寫鎖是怎麼實現的?

3jvm是怎樣優化synchronized?

跑題時間:今年的互聯網圈比較“火”哈,知乎帶頭,什麼美團,摩拜也緊跟其後,ofo更不用提了,公司算是黃了,可憐我的99元的身家性命打水漂了啊。人員優化不斷,給互聯網技術人員好好上了一課,無論你業務代碼寫得多流弊,公司砍掉業務線,你什麼都不是,你只不過是一個高級點的奴隸罷了,我好幾個小夥伴都是在找工作,但是面下來的結果是技術和錢是不成正比的,淺的人家2-3年的人就把活幹了,深的人家一問只知道概念,具體怎麼實現的要麼不知道,要麼看過忘記了,所以面試高級以上很難面,除非熟人對你知根知底的。我算是看透了,今年現在好好沉澱點技術,找準技術突破點成爲有一技之長的技術人才是永恆之道。

參考博客:
https://tech.meituan.com/Java_Lock.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章