日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

一文带你理解Java中Lock的实现原理

發布時間:2023/12/3 java 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一文带你理解Java中Lock的实现原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載自? ?一文帶你理解Java中Lock的實現原理

當多個線程需要訪問某個公共資源的時候,我們知道需要通過加鎖來保證資源的訪問不會出問題。java提供了兩種方式來加鎖,一種是關鍵字:synchronized,一種是concurrent包下的lock鎖。synchronized是java底層支持的,而concurrent包則是jdk實現。關于synchronized的原理可以閱讀再有人問你synchronized是什么,就把這篇文章發給他。

在這里,我會用盡可能少的代碼,盡可能輕松的文字,盡可能多的圖來看看lock的原理。

我們以ReentrantLock為例做分析,其他原理類似。

我把這個過程比喻成一個做菜的過程,有什么菜,做法如何?

我先列出lock實現過程中的幾個關鍵詞:計數值、雙向鏈表、CAS+自旋

使用例子

import?java.util.concurrent.locks.ReentrantLock;public?class?App?{public?static?void?main(String[]?args)?throws?Exception?{final?int[]?counter?=?{0};ReentrantLock?lock?=?new?ReentrantLock();for?(int?i=?0;?i?<?50;?i++){new?Thread(new?Runnable()?{@Overridepublic?void?run()?{lock.lock();try?{int?a?=?counter[0];counter[0]?=?a?+?1;}finally?{lock.unlock();}}}).start();}//?主線程休眠,等待結果Thread.sleep(5000);System.out.println(counter[0]);} }

在這個例子中,開50個線程同時更新counter。分成三塊來看看源碼(初始化、獲取鎖、釋放鎖)

?

實現原理

ReentrantLock() 干了啥

?/***?Creates?an?instance?of?{@code?ReentrantLock}.*?This?is?equivalent?to?using?{@code?ReentrantLock(false)}.*/public?ReentrantLock()?{sync?=?new?NonfairSync();}

在lock的構造函數中,定義了一個NonFairSync,

static?final?class?NonfairSync?extends?Sync?

NonfairSync 又是繼承于Sync

abstract?static?class?Sync?extends?AbstractQueuedSynchronizer

一步一步往上找,找到了
這個鬼AbstractQueuedSynchronizer(簡稱AQS),最后這個鬼,又是繼承于AbstractOwnableSynchronizer(AOS),AOS主要是保存獲取當前鎖的線程對象,代碼不多不再展開。
最后我們可以看到幾個主要類的繼承關系。

?

鎖的類的繼承關系

FairSync 與 NonfairSync的區別在于,是不是保證獲取鎖的公平性,因為默認是NonfairSync,我們以這個為例了解其背后的原理。

其他幾個類代碼不多,最后的主要代碼都是在AQS中,我們先看看這個類的主體結構。

AbstractQueuedSynchronizer是個什么

再看看Node是什么?

看到這里的同學,是不是有種熱淚盈眶的感覺,這尼瑪,不就是雙向鏈表么?我還記得第一次寫這個數據結構的時候,發現居然還有這么神奇的一個東西。

最后我們可以發現鎖的存儲結構就兩個東西:"雙向鏈表" + "int類型狀態"。
需要注意的是,他們的變量都被"transient和volatile修飾。

一個int值,一個雙向鏈表是如何烹飪處理鎖這道菜的呢,Doug Lea大神就是大神,我們接下來看看,如何獲取鎖?

lock.lock()怎么獲取鎖?

/***?Acquires?the?lock.*/ public?void?lock()?{sync.lock(); }

可以看到調用的是,NonfairSync.lock()

看到這里,我們基本有了一個大概的了解,還記得之前AQS中的int類型的state值,這里就是通過CAS(樂觀鎖)去修改state的值。lock的基本操作還是通過樂觀鎖來實現的

獲取鎖通過CAS,那么沒有獲取到鎖,等待獲取鎖是如何實現的?我們可以看一下else分支的邏輯,acquire方法:

public?final?void?acquire(int?arg)?{if?(!tryAcquire(arg)?&&acquireQueued(addWaiter(Node.EXCLUSIVE),?arg))selfInterrupt(); }

這里干了三件事情:

  • tryAcquire:會嘗試再次通過CAS獲取一次鎖。

  • addWaiter:將當前線程加入上面鎖的雙向鏈表(等待隊列)中

  • acquireQueued:通過自旋,判斷當前隊列節點是否可以獲取鎖。

addWaiter 添加當前線程到等待鏈表中

可以看到,通過CAS確保能夠在線程安全的情況下,將當前線程加入到鏈表的尾部。
enq是個自旋+上述邏輯,有興趣的可以翻翻源碼。

acquireQueued

自旋+CAS嘗試獲取鎖
可以看到,當當前線程到頭部的時候,嘗試CAS更新鎖狀態,如果更新成功表示該等待線程獲取成功。從頭部移除。

?

每一個線程都在自旋+CAS

最后簡要概括一下,獲取鎖的一個流程

獲取鎖流程

lock.unlock() 釋放鎖

public?void?unlock()?{sync.release(1); }

可以看到調用的是,NonfairSync.release()

最后有調用了NonfairSync.tryRelease()

基本可以確認,釋放鎖就是對AQS中的狀態值State進行修改。同時更新下一個鏈表中的線程等待節點。

?

總結

  • lock的存儲結構:一個int類型狀態值(用于鎖的狀態變更),一個雙向鏈表(用于存儲等待中的線程)

  • lock獲取鎖的過程:本質上是通過CAS來獲取狀態值修改,如果當場沒獲取到,會將該線程放在線程等待鏈表中。

  • lock釋放鎖的過程:修改狀態值,調整等待鏈表。

  • 可以看到在整個實現過程中,lock大量使用CAS+自旋。因此根據CAS特性,lock建議使用在低鎖沖突的情況下。目前java1.6以后,官方對synchronized做了大量的鎖優化(偏向鎖、自旋、輕量級鎖)。因此在非必要的情況下,建議使用synchronized做同步操作。

總結

以上是生活随笔為你收集整理的一文带你理解Java中Lock的实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。