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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

深入Lock锁底层原理实现,手写一个可重入锁

發布時間:2025/3/21 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入Lock锁底层原理实现,手写一个可重入锁 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

synchronized與lock

lock是一個接口,而synchronized是在JVM層面實現的。synchronized釋放鎖有兩種方式:

  • 獲取鎖的線程執行完同步代碼,釋放鎖 。

  • 線程執行發生異常,jvm會讓線程釋放鎖。

  • lock鎖的釋放,出現異常時必須在finally中釋放鎖,不然容易造成線程死鎖。lock顯式獲取鎖和釋放鎖,提供超時獲取鎖、可中斷地獲取鎖。

    synchronized是以隱式地獲取和釋放鎖,synchronized無法中斷一個正在等待獲取鎖的線程。

    synchronized原始采用的是CPU悲觀鎖機制,即線程獲得的是獨占鎖。獨占鎖意味著其他線程只能依靠阻塞來等待線程釋放鎖。而在CPU轉換線程阻塞時會引起線程上下文切換,當有很多線程競爭鎖的時候,會引起CPU頻繁的上下文切換導致效率很低。

    Lock用的是樂觀鎖方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。樂觀鎖實現的機制就是CAS操作。

    具體的悲觀鎖和樂觀鎖的詳細介紹請參考這篇文章[淺談數據庫樂觀鎖、悲觀鎖]

    在JDK5中增加了一個Lock接口實現類ReentrantLock.它不僅擁有和synchronized相同的并發性和內存語義,還多了鎖投票,定時鎖,等候和中斷鎖等.它們的性能在不同的情況下會有不同。

    在資源競爭不是很激烈的情況下,synchronized的性能要由于ReentrantLock,但是在資源競爭很激烈的情況下,synchronized的性能會下降得非常快,而ReentrantLock的性能基本保持不變.

    接下來我們會進一步研究ReentrantLock的源代碼,會發現其中比較重要的獲得鎖的一個方法是compareAndSetState。

    lock源碼

    在閱讀源碼的成長的過程中,有很多人會遇到很多困難,一個是源碼太多,另一方面是源碼看不懂。在閱讀源碼方面,我提供一些個人的建議:

  • 第一個是抓主舍次,看源碼的時候,很多人會發現源碼太長太多,看不下去,這就要求我們抓住哪些是核心的方法,哪些是次要的方法。當舍去次要方法,就會發現代碼精簡和很多,會大大提高我們閱讀源碼的信心。

  • 第二個是不要死扣,有人看源碼會一行一行的死扣,當看到某一行看不懂,就一直停在那里死扣,知道看懂為止,其實很多時候,雖然看不懂代碼,但是可以從變量名和方法名知道該代碼的作用,java中都是見名知意的。

  • 接下來進入閱讀lock的源碼部分,在lock的接口中,主要的方法如下:

    public?interface?Lock?{//?加鎖void?lock();//?嘗試獲取鎖boolean?tryLock();boolean?tryLock(long?time,?TimeUnit?unit)?throws?InterruptedException;//?解鎖void?unlock(); }

    在lock接口的實現類中,最主要的就是ReentrantLock,來看看ReentrantLock中lock()方法的源碼:

    ????//?默認構造方法,非公平鎖public?ReentrantLock()?{sync?=?new?NonfairSync();}//?構造方法,公平鎖public?ReentrantLock(boolean?fair)?{sync?=?fair???new?FairSync()?:?new?NonfairSync();}//?加鎖public?void?lock()?{sync.lock();}

    在初始化lock實例對象的時候,可以提供一個boolean的參數,也可以不提供該參數。提供該參數就是公平鎖,不提供該參數就是非公平鎖。

    什么是非公平鎖和公平鎖呢?

    非公平鎖就是不按照線程先來后到的時間順序進行競爭鎖,后到的線程也能夠獲取到鎖,公平鎖就是按照線程先來后到的順序進行獲取鎖,后到的線程只能等前面的線程都獲取鎖完畢才執行獲取鎖的操作,執行有序。

    我們來看看lock()這個方法,這個有區分公平鎖和非公平鎖,這個兩者的實現不同,先來看看公平鎖,源碼如下:

    //?直接調用?acquire(1) final?void?lock()?{acquire(1);}

    我們來看看acquire(1)的源碼如下:

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

    這里的判斷條件主要做兩件事:

  • 通關過該方法tryAcquire(arg)嘗試的獲取鎖

  • 若是沒有獲取到鎖,通過該方法acquireQueued(addWaiter(Node.EXCLUSIVE), arg)就將當前的線程加入到存儲等待線程的隊列中。

  • 其中tryAcquire(arg)是嘗試獲取鎖,這個方法是公平鎖的核心之一,它的源碼如下:

    protected?final?boolean?tryAcquire(int?acquires)?{//?獲取當前線程?final?Thread?current?=?Thread.currentThread();//?獲取當前線程擁有著的狀態int?c?=?getState();//?若為0,說明當前線程擁有著已經釋放鎖if?(c?==?0)?{//?判斷線程隊列中是否有,排在前面的線程等待著鎖,若是沒有設置線程的狀態為1。if?(!hasQueuedPredecessors()?&&compareAndSetState(0,?acquires))?{//?設置線程的擁有著為當前線程setExclusiveOwnerThread(current);return?true;}//?若是當前的線程的鎖的擁有者就是當前線程,可重入鎖}?else?if?(current?==?getExclusiveOwnerThread())?{//?執行狀態值+1int?nextc?=?c?+?acquires;if?(nextc?<?0)throw?new?Error("Maximum?lock?count?exceeded");//?設置status的值為nextcsetState(nextc);return?true;}return?false;}

    在tryAcquire()方法中,主要是做了以下幾件事:

  • 判斷當前線程的鎖的擁有者的狀態值是否為0,若為0,通過該方法hasQueuedPredecessors()再判斷等待線程隊列中,是否存在排在前面的線程。

  • 若是沒有通過該方法?compareAndSetState(0, acquires)設置當前的線程狀態為1。

  • 將線程擁有者設為當前線程setExclusiveOwnerThread(current)

  • 若是當前線程的鎖的擁有者的狀態值不為0,說明當前的鎖已經被占用,通過current == getExclusiveOwnerThread()判斷鎖的擁有者的線程,是否為當前線程,實現鎖的可重入。

  • 若是當前線程將線程的狀態值+1,并更新狀態值。

  • 公平鎖的tryAcquire(),實現的原理圖如下:

    我們來看看acquireQueued()方法,該方法是將線程加入等待的線程隊列中,源碼如下:

    final?boolean?acquireQueued(final?Node?node,?int?arg)?{boolean?failed?=?true;try?{boolean?interrupted?=?false;//?死循環處理for?(;;)?{//?獲取前置線程節點final?Node?p?=?node.predecessor();//?這里又嘗試的去獲取鎖if?(p?==?head?&&?tryAcquire(arg))?{setHead(node);p.next?=?null;?//?help?GCfailed?=?false;//?直接return??interruptedreturn?interrupted;}//?在獲取鎖失敗后,應該將線程Park(暫停)if?(shouldParkAfterFailedAcquire(p,?node)?&&parkAndCheckInterrupt())interrupted?=?true;}}?finally?{if?(failed)cancelAcquire(node);}}

    acquireQueued()方法主要執行以下幾件事:

  • 死循環處理等待線程中的前置節點,并嘗試獲取鎖,若是p == head &amp;&amp; tryAcquire(arg),則跳出循環,即獲取鎖成功。

  • 若是獲取鎖不成功shouldParkAfterFailedAcquire(p, node) &amp;&amp;parkAndCheckInterrupt()就會將線程暫停。

  • 在acquire(int arg)方法中,最后若是條件成立,執行下面的源碼:

    selfInterrupt();//?實際執行的代碼為 Thread.currentThread().interrupt();

    即嘗試獲取鎖失敗,就會將鎖加入等待的線程隊列中,并讓線程處于中斷等待。公平鎖lock()方法執行的原理圖如下:


    之所以畫這些原理的的原因,是為后面寫一個自己的鎖做鋪墊,因為你要實現和前人差不多的東西,你必須了解該東西執行的步驟,最后得出的結果,執行的過程是怎么樣的。

    有了流程圖,在后面的實現自己的東西才能一步一步的進行。這也是閱讀源碼的必要之一。

    在lock()方法,其實在lock()方法中,已經包含了兩方面:

  • 鎖方法lock()。

  • 嘗試獲取鎖方法tryAquire()。

  • 接下來,我們來看一下unlock()方法的源碼。

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

    直接調用release(1)方法,來看release方法源碼如下:

    ????public?final?boolean?release(int?arg)?{//?嘗試釋放當前節點if?(tryRelease(arg))?{//?取出頭節點Node?h?=?head;if?(h?!=?null?&&?h.waitStatus?!=?0)//?釋放鎖后要即使喚醒等待的線程來獲取鎖unparkSuccessor(h);return?true;}return?false;}

    通過調用tryRelease(arg),嘗試釋放當前節點,若是釋放鎖成功,就會獲取的等待隊列中的頭節點,就會即使喚醒等待隊列中的等待線程來獲取鎖。接下來看看tryRelease(arg)的源碼如下:

    //?嘗試釋放鎖protected?final?boolean?tryRelease(int?releases)?{//?將當前狀態值-1int?c?=?getState()?-?releases;//?判斷當前線程是否是鎖的擁有者,若不是直接拋出異常,非法操作,直接一點的解釋就是,你都沒有擁有鎖,還來釋放鎖,這不是騙人的嘛if?(Thread.currentThread()?!=?getExclusiveOwnerThread())throw?new?IllegalMonitorStateException();boolean?free?=?false;//執行釋放鎖操作?1.若狀態值=0???2.將當前的鎖的擁有者設為nullif?(c?==?0)?{free?=?true;setExclusiveOwnerThread(null);}//?重新更新status的狀態值setState(c);return?free;}

    總結上面的幾個方法,unlock釋放鎖方法的執行原理圖如下:


    對于非公平鎖與公平鎖的區別,在非公平鎖嘗試獲取鎖中不會執行hasQueuedPredecessors()去判斷是否隊列中還有等待的前置節點線程。

    如下面的非公平鎖,嘗試獲取鎖nonfairTryAcquire()源碼如下:

    final?boolean?nonfairTryAcquire(int?acquires)?{final?Thread?current?=?Thread.currentThread();int?c?=?getState();if?(c?==?0)?{//?直接就將status-1,并不會判斷是否還有前置線程在等待if?(compareAndSetState(0,?acquires))?{setExclusiveOwnerThread(current);return?true;}}else?if?(current?==?getExclusiveOwnerThread())?{int?nextc?=?c?+?acquires;if?(nextc?<?0)?//?overflowthrow?new?Error("Maximum?lock?count?exceeded");setState(nextc);return?true;}return?false;}

    以上就是公平鎖和非公平鎖的主要的核心方法的源碼,接下來我們實現自己的一個鎖,首先依據前面的分析中,要實現自己的鎖,擁有的鎖的核心屬性如下:

  • 狀態值status,0為未占用鎖,1未占用鎖,并且是線程安全的。

  • 等待線程隊列,用于存放獲取鎖的等待線程。

  • 當前線程的擁有者。

  • lock鎖的核心的Api如下:

  • lock方法

  • trylock方法

  • unlock方法

  • 依據以上的核心思想來實現自己的鎖,首先定義狀態值status,使用的是AtomicInteger原子變量來存放狀態值,實現該狀態值的并發安全和可見性。定義如下:

    //?線程的狀態?0表示當前沒有線程占用???1表示有線程占用 AtomicInteger?status?=new?AtomicInteger();

    接下來定義等待線程隊列,使用LinkedBlockingQueue隊列來裝線程,定義如下:

    //?等待的線程 LinkedBlockingQueue<Thread>?waiters?=?new?LinkedBlockingQueue<Thread>();

    最后的屬性為當前鎖的擁有者,直接就用Thread來封裝,定義如下:

    //?當前線程擁有者 Thread?ownerThread?=null;

    接下來定義lock()方法,依據上面的源碼分析,在lock方法中主要執行的幾件事如下:

  • 死循環的處理等待線程隊列中的線程,知道獲取鎖成功,將該線程從隊列中刪除,跳出循環。

  • 獲取鎖不成功,線程處于暫停等待。

  • ????@Overridepublic?void?lock()?{//?TODO?Auto-generated?method?stub//?嘗試獲取鎖if?(!tryLock())?{//?獲取鎖失敗,將鎖加入等待的隊列中waitersQueue.add(Thread.currentThread());//?死循環處理隊列中的鎖,不斷的獲取鎖for?(;;)?{if?(tryLock())?{//?直到獲取鎖成功,將該線程從等待隊列中刪除waitersQueue.poll();//?直接返回return;}?else?{//?獲取鎖不成功,就直接暫停等待。LockSupport.park();}}}}

    然后是trylock方法,依據上面的源碼分析,在trylock中主要執行的以下幾件事:

  • 判斷當前擁有鎖的線程的狀態是否為0,為0,執行狀態值+1,并將當前線程設置為鎖擁有者。

  • 實現鎖可重入

  • ????@Overridepublic?boolean?tryLock()?{//?判斷是否有現成占用if?(status.get()==0)?{//?執行狀態值加1if?(status.compareAndSet(0,?1))?{//?將當前線程設置為鎖擁有者ownerThread?=?Thread.currentThread();return?true;}?else?if(ownerThread==Thread.currentThread())??{//?實現鎖可重入status.set(status.get()+1);}}return?false;}

    最后就是unlock方法,依據上面的源碼分析,在unlock中主要執行的事情如下:

  • 判斷當前線程是否是鎖擁有者,若不是直接拋出異常。

  • 判斷狀態值是否為0,并將鎖擁有者清空,喚醒等待的線程。

  • ????@Overridepublic?void?unlock()?{//?TODO?Auto-generated?method?stub//?判斷當前線程是否是鎖擁有者if?(ownerThread!=Thread.currentThread())?{throw?new?RuntimeException("非法操作");}//?判斷狀態值是否為0if?(status.decrementAndGet()==0)?{//?清空鎖擁有著ownerThread?=?null;//?從等待隊列中獲取前置線程Thread?t?=?waitersQueue.peek();if?(t!=null)?{//?并立即喚醒該線程LockSupport.unpark(t);}}}

    以上就是實現自己的非公平的可重入鎖,lock的源碼其實并不復雜,只要認真看都能看懂,在閱讀源碼的過程中,會遇到比較復雜的問題。遇到問題不要慌,網上查詢資料,相信很多都能找到答案,因為java的生態如此完善,幾乎90%的東西網上都會有,只要沉得住氣,相信一定會有所收獲。

    總結

    以上是生活随笔為你收集整理的深入Lock锁底层原理实现,手写一个可重入锁的全部內容,希望文章能夠幫你解決所遇到的問題。

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