日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

理解鎖的基礎知識

如果想要透徹的理解JAVA鎖的來龍去脈,需要先了解以下基礎知識。

基礎知識之一:鎖的類型

按照其性質分類

1)公平鎖/非公平鎖

公平鎖是指多個線程按照申請鎖的順序來獲取鎖。非公平鎖是指多個線程獲取鎖的順序并不是按照申請鎖的順序,有可能后申請的線程比先申請的線程優先獲取鎖。有可能,會造成優先級反轉或者饑餓現象。

對于JavaReentrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在于吞吐量比公平鎖大。對于Synchronized而言,也是一種非公平鎖。

由于其并不像ReentrantLock是通過AQS的來實現線程調度,所以并沒有任何辦法使其變成公平鎖。

2)樂觀鎖/悲觀鎖

樂觀鎖與悲觀鎖不是指具體的什么類型的鎖,而是指看待并發同步的角度。

悲觀鎖認為對于同一個數據的并發操作,一定是會發生修改的,哪怕沒有修改,也會認為修改。因此對于同一個數據的并發操作,悲觀鎖采取加鎖的形式。悲觀的認為,不加鎖的并發操作一定會出問題。

樂觀鎖則認為對于同一個數據的并發操作,是不會發生修改的。在更新數據的時候,會采用嘗試更新,不斷重新的方式更新數據。樂觀的認為,不加鎖的并發操作是沒有事情的。

從上面的描述我們可以看出,悲觀鎖適合寫操作非常多的場景,樂觀鎖適合讀操作非常多的場景,不加鎖會帶來大量的性能提升。

悲觀鎖在Java中的使用,就是利用各種鎖。樂觀鎖在Java中的使用,是無鎖編程,常常采用的是CAS算法,典型的例子就是原子類,通過CAS自旋實現原子操作的更新。

3)獨享鎖/共享鎖

獨享鎖是指該鎖一次只能被一個線程所持有。共享鎖是指該鎖可被多個線程所持有。對于JavaReentrantLock而言,其是獨享鎖。但是對于Lock的另一個實現類ReentrantReadWriteLock,其讀鎖是共享鎖,其寫鎖是獨享鎖。

讀鎖的共享鎖可保證并發讀是非常高效的,讀寫,寫讀 ,寫寫的過程是互斥的。

獨享鎖與共享鎖也是通過AQS來實現的,通過實現不同的方法,來實現獨享或者共享。對于Synchronized而言,當然是獨享鎖。

4)互斥鎖/讀寫鎖

上面講的獨享鎖/共享鎖就是一種廣義的說法,互斥鎖/讀寫鎖就是具體的實現。互斥鎖在Java中的具體實現就是ReentrantLock,讀寫鎖在Java中的具體實現就是ReentrantReadWriteLock

5)可重入鎖

可重入鎖又名遞歸鎖,是指在同一個線程在外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖。

對于Java ReentrantLock而言, 他的名字就可以看出是一個可重入鎖,其名字是Reentrant Lock重新進入鎖。對于Synchronized而言,也是一個可重入鎖。可重入鎖的一個好處是可一定程度避免死鎖。

基礎知識之二:java線程阻塞的代價

java的線程是映射到操作系統原生線程之上的,如果要阻塞或喚醒一個線程就需要操作系統介入,需要在戶態與核心態之間切換,這種切換會消耗大量的系統資源,因為用戶態與內核態都有各自專用的內存空間,專用的寄存器等,用戶態切換至內核態需要傳遞給許多變量、參數給內核,內核也需要保護好用戶態在切換時的一些寄存器值、變量等,以便內核態調用結束后切換回用戶態繼續工作。

如果線程狀態切換是一個高頻操作時,這將會消耗很多CPU處理時間;

如果對于那些需要同步的簡單的代碼塊,獲取鎖掛起操作消耗的時間比用戶代碼執行的時間還要長,這種同步策略顯然非常糟糕的。

synchronized會導致爭用不到鎖的線程進入阻塞狀態,所以說它是java語言中一個重量級的同步操縱,被稱為重量級鎖,為了緩解上述性能問題,JVM從1.5開始,引入了輕量鎖與偏向鎖,默認啟用了自旋鎖,他們都屬于樂觀鎖。

明確java線程切換的代價,是理解java中各種鎖的優缺點的基礎之一。

基礎知識之三:CAS

CAS(Compare and swap)比較和替換是設計并發算法時用到的一種技術。簡單來說,比較和替換是使用一個期望值和一個變量的當前值進行比較,如果當前變量的值與我們期望的值相等,就使用一個新值替換當前變量的值。

Java5以來,你可以使用
java.util.concurrent.atomic

中的一些原子類來使用CPU中的這些功能:

private AtomicBoolean locked = new AtomicBoolean(false);
public boolean lock() {
return locked.compareAndSet(false, true);

locked變量不再是boolean類型而是AtomicBoolean。這個類中有一個compareAndSet()方法,它使用一個期望值和AtomicBoolean實例的值比較,和兩者相等,則使用一個新值替換原來的值。在這個例子中,它比較locked的值和false,如果locked的值為false,則把修改為true。

如果值被替換了,compareAndSet()返回true,否則,返回false。

CAS的ABA問題

1)進程P1在共享變量中讀到值為A;

2)P1被搶占了,進程P2執行;

3)P2把共享變量里的值從A改成了B,再改回到A,此時被P1搶占;

4)P1回來看到共享變量里的值沒有被改變,于是繼續執行;

這個例子你可能沒有看懂,維基百科上給了一個活生生的例子——

 

你拿著一個裝滿錢的手提箱在飛機場,此時過來了一個火辣性感的美女, 然后她很暖昧地挑逗著你,并趁你不注意的時候,把用一個一模一樣的 手提箱和你那裝滿錢的箱子調了個包,然后就離開了,你看到你的手提 箱還在那,于是就提著手提箱去趕飛機去了。

 

小結

前面提到了java的4種鎖,他們分別是重量級鎖、自旋鎖、輕量級鎖和偏向鎖,不同的鎖有不同特點,每種鎖只有在其特定的場景下,才會有出色的表現,java中沒有哪種鎖能夠在所有情況下都能有出色的效率,引入這么多鎖的原因就是為了應對不同的情況;

前面講到了重量級鎖是悲觀鎖的一種,自旋鎖、輕量級鎖與偏向鎖屬于樂觀鎖,所以現在你就能夠大致理解了他們的適用范圍,但是具體如何使用這幾種鎖呢,就要看后面的具體分析他們的特性。

synchronized的實現機制

Java SE1.6里synchronized一共有四種狀態,無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態,它會隨著競爭情況逐漸升級

鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。

自旋鎖

自旋鎖原理非常簡單,如果持有鎖的線程能在很短時間內釋放鎖資源,那么那些等待競爭鎖的線程就不需要做內核態和用戶態之間的切換進入阻塞掛起狀態,它們只需要等一等(自旋),等持有鎖的線程釋放鎖后即可立即獲取鎖,這樣就避免用戶線程和內核的切換的消耗

但是線程自旋是需要消耗cpu的,說白了就是讓cpu在做無用功,如果一直獲取不到鎖,那線程也不能一直占用cpu自旋做無用功,所以需要設定一個自旋等待的最大時間。

如果持有鎖的線程執行的時間超過自旋等待的最大時間扔沒有釋放鎖,就會導致其它爭用鎖的線程在最大等待時間內還是獲取不到鎖,這時爭用線程會停止自旋進入阻塞狀態。

1)自旋鎖的優缺點

自旋鎖盡可能的減少線程的阻塞,這對于鎖的競爭不激烈,且占用鎖時間非常短的代碼塊來說性能能大幅度的提升,因為自旋的消耗會小于線程阻塞掛起再喚醒的操作的消耗,這些操作會導致線程發生兩次上下文切換!

但是如果鎖的競爭激烈,或者持有鎖的線程需要長時間占用鎖執行同步塊,這時候就不適合使用自旋鎖了,因為自旋鎖在獲取鎖前一直都是占用cpu做無用功,占著XX不XX,同時有大量線程在競爭一個鎖,會導致獲取鎖的時間很長,線程自旋的消耗大于線程阻塞掛起操作的消耗,其它需要cpu的線程又不能獲取到cpu,造成cpu的浪費。所以這種情況下我們要關閉自旋鎖。

2)自旋鎖的開啟

JDK1.6-XX:+UseSpinning開啟;

-XX:PreBlockSpin=10為自旋次數;

JDK1.7后,去掉此參數,由jvm控制。

偏向鎖

Java偏向鎖(Biased Locking)是Java6引入的一項多線程優化。

偏向鎖,顧名思義,它會偏向于第一個訪問鎖的線程。

大多數情況下鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得。偏向鎖的目的是在某個線程獲得鎖之后,消除這個線程鎖重入(CAS)的開銷,看起來讓這個線程得到了偏護。

另外,JVM對那種會有多線程加鎖,但不存在鎖競爭的情況也做了優化,聽起來比較拗口,但在現實應用中確實是可能出現這種情況,因為線程之前除了互斥之外也可能發生同步關系,被同步的兩個線程(一前一后)對共享對象鎖的競爭很可能是沒有沖突的。

對這種情況,JVM用一個epoch表示一個偏向鎖的時間戳(真實地生成一個時間戳代價還是蠻大的,因此這里應當理解為一種類似時間戳的identifier)。

如果在運行過程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會被掛起,JVM會消除它身上的偏向鎖,將鎖恢復到標準的輕量級鎖。

它通過消除資源無競爭情況下的同步,進一步提高了程序的運行性能。

1)偏向鎖的獲取

訪問Mark word中偏向鎖的標識是否設置成1,鎖標志位是否為01,確認為可偏向狀態。

如果為可偏向狀態,則測試線程ID是否指向當前線程,如果是,進入步驟5,否則進入步驟3。

如果線程ID并未指向當前線程,則通過CAS操作競爭鎖。如果競爭成功,則將Mark Word中線程ID設置為當前線程ID,然后執行5;如果競爭失敗,執行4。

如果CAS獲取偏向鎖失敗,則表示有競爭。當到達全局安全點(safepoint)時獲得偏向鎖的線程被掛起,偏向鎖升級為輕量級鎖,然后被阻塞在安全點的線程繼續往下執行同步代碼。(撤銷偏向鎖的時候會導致stop the word)

執行同步代碼。

2)偏向鎖的釋放

偏向鎖的撤銷在上述第四步驟中有提到。偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖,線程不會主動去釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有字節碼正在執行),它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處于被鎖定狀態,撤銷偏向鎖后恢復到未鎖定(標志位為“01”)或輕量級鎖(標志位為“00”)的狀態。

3)偏向鎖的適用場景

始終只有一個線程在執行同步塊,在它沒有執行完釋放鎖之前,沒有其它線程去執行同步塊,在鎖無競爭的情況下使用,一旦有了競爭就升級為輕量級鎖,升級為輕量級鎖的時候需要撤銷偏向鎖,撤銷偏向鎖的時候會導致stop the word操作;

在有鎖的競爭時,偏向鎖會多做很多額外操作,尤其是撤銷偏向所的時候會導致進入安全點,安全點會導致stw,導致性能下降,這種情況下應當禁用;

4)jvm開啟/關閉偏向鎖

開啟偏向鎖:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

關閉偏向鎖:-XX:-UseBiasedLocking

輕量級鎖

輕量級鎖是由偏向所升級來的,偏向鎖運行在一個線程進入同步塊的情況下,當第二個線程加入鎖爭用的時候,偏向鎖就會升級為輕量級鎖;跟多關于鎖面試資料,公眾 號Java精選,回復java面試,獲取面試資料。

1)加鎖

線程在執行同步塊之前,JVM會先在當前線程的棧楨中創建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復制到鎖記錄中,官方稱為Displaced Mark Word

然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,則自旋獲取鎖,當自旋獲取鎖仍然失敗時,表示存在其他線程競爭鎖(兩條或兩條以上的線程競爭同一個鎖),則輕量級鎖會膨脹成重量級鎖。

2)解鎖

輕量級解鎖時,會使用原子的CAS操作來將Displaced Mark Word替換回到對象頭,如果成功,則表示同步過程已完成。如果失敗,表示有其他線程嘗試過獲取該鎖,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。則要在釋放鎖的同時喚醒被掛起的線程。

總結

偏向鎖/輕量級鎖/重量級鎖這三種鎖是指鎖的狀態,并且是針對Synchronized。在Java 5通過引入鎖升級的機制來實現高效Synchronized。這三種鎖的狀態是通過對象監視器在對象頭中的字段來表明的。

第一步,檢查MarkWord里面是不是放的自己的ThreadId,如果是,表示當前線程是處于 “偏向鎖”.跳過輕量級鎖直接執行同步體。

第二步,如果MarkWord不是自己的ThreadId,鎖升級,這時候,用CAS來執行切換,新的線程根據MarkWord里面現有的ThreadId,通知之前線程暫停,之前線程將Markword的內容置為空。

第三步,兩個線程都把對象的HashCode復制到自己新建的用于存儲鎖的記錄空間,接著開始通過CAS操作,把共享對象的MarKword的內容修改為自己新建的記錄空間的地址的方式競爭MarkWord。

第四步,第三步中成功執行CAS的獲得資源,失敗的則進入自旋。

第五步,自旋的線程在自旋過程中,成功獲得資源(即之前獲的資源的線程執行完成并釋放了共享資源),則整個狀態依然處于輕量級鎖的狀態,如果自旋失敗 第六步,進入重量級鎖的狀態,這個時候,自旋的線程進行阻塞,等待之前線程執行完成并喚醒自己。

如果線程爭用激烈,那么應該禁用偏向鎖。

鎖優化

以上介紹的鎖不是我們代碼中能夠控制的,但是借鑒上面的思想,我們可以優化我們自己線程的加鎖操作;

減少鎖的時間

不需要同步執行的代碼,能不放在同步快里面執行就不要放在同步快內,可以讓鎖盡快釋放;

減少鎖的粒度

它的思想是將物理上的一個鎖,拆成邏輯上的多個鎖,增加并行度,從而降低鎖競爭。它的思想也是用空間來換時間;

java中很多數據結構都是采用這種方法提高并發操作的效率:

ConcurrentHashMap的鎖分段技術

LinkedBlockingQueue也體現了這樣的思想,在隊列頭入隊,在隊列尾出隊,入隊和出隊使用不同的鎖,相對于LinkedBlockingArray只有一個鎖效率要高;

鎖粗化

鎖的粗化則是要增大鎖的粒度;

在以下場景下需要粗化鎖的粒度:

假如有一個循環,循環內的操作需要加鎖,我們應該把鎖放到循環外面,否則每次進出循環,都進出一次臨界區,效率是非常差的;

使用讀寫鎖

ReentrantReadWriteLock是一個讀寫鎖,讀操作加讀鎖,可以并發讀,寫操作使用寫鎖,只能單線程寫;

讀寫分離

CopyOnWriteArrayListCopyOnWriteArraySet

我們可以對CopyOnWrite容器進行并發的讀,而不需要加鎖,因為當前容器不會添加任何元素,而是操作容器的副本。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。

使用cas

如果需要同步的操作執行速度非常快,并且線程競爭并不激烈,這時候使用cas效率會更高,因為加鎖會導致線程的上下文切換,如果上下文切換的耗時比同步操作本身更耗時,且線程對資源的競爭不激烈,使用volatiled+cas操作會是非常高效的選擇。

 

作者:Myosotis https://segmentfault.com/a/1190000013512810

 

公眾號“Java精選”所發表內容注明來源的,版權歸原出處所有(無法查證版權的或者未注明出處的均來自網絡,系轉載,轉載的目的在于傳遞更多信息,版權屬于原作者。如有侵權,請聯系,筆者會第一時間刪除處理!

最近有很多人問,有沒有讀者交流群!加入方式很簡單,公眾號Java精選,回復“加群”,即可入群!

(微信小程序):3000+道面試題,包含Java基礎、并發、JVM、線程、MQ系列、redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架構設計等,在線隨時刷題!

------ 特別推薦 ------

特別推薦:專注分享最前沿的技術與資訊,為彎道超車做好準備及各種開源項目與高效率軟件的公眾號,「大咖筆記」,專注挖掘好東西,非常值得大家關注。點擊下方公眾號卡片關注

文章有幫助的話,點在看,轉發吧!

分享到:
標簽:Java
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定