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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

java 同步锁_死磕 java同步系列之自己动手写一个锁Lock

發(fā)布時(shí)間:2024/7/23 编程问答 61 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 同步锁_死磕 java同步系列之自己动手写一个锁Lock 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

問題

(1)自己動(dòng)手寫一個(gè)鎖需要哪些知識(shí)?

(2)自己動(dòng)手寫一個(gè)鎖到底有多簡(jiǎn)單?

(3)自己能不能寫出來一個(gè)完美的鎖?

簡(jiǎn)介

本篇文章的目標(biāo)一是自己動(dòng)手寫一個(gè)鎖,這個(gè)鎖的功能很簡(jiǎn)單,能進(jìn)行正常的加鎖、解鎖操作。

本篇文章的目標(biāo)二是通過自己動(dòng)手寫一個(gè)鎖,能更好地理解后面章節(jié)將要學(xué)習(xí)的AQS及各種同步器實(shí)現(xiàn)的原理。

分析

自己動(dòng)手寫一個(gè)鎖需要準(zhǔn)備些什么呢?

首先,在上一章學(xué)習(xí)synchronized的時(shí)候我們說過它的實(shí)現(xiàn)原理是更改對(duì)象頭中的MarkWord,標(biāo)記為已加鎖或未加鎖。

但是,我們自己是無法修改對(duì)象頭信息的,那么我們可不可以用一個(gè)變量來代替呢?

比如,這個(gè)變量的值為1的時(shí)候就說明已加鎖,變量值為0的時(shí)候就說明未加鎖,我覺得可行。

其次,我們要保證多個(gè)線程對(duì)上面我們定義的變量的爭(zhēng)用是可控的,所謂可控即同時(shí)只能有一個(gè)線程把它的值修改為1,且當(dāng)它的值為1的時(shí)候其它線程不能再修改它的值,這種是不是就是典型的CAS操作,所以我們需要使用Unsafe這個(gè)類來做CAS操作。

然后,我們知道在多線程的環(huán)境下,多個(gè)線程對(duì)同一個(gè)鎖的爭(zhēng)用肯定只有一個(gè)能成功,那么,其它的線程就要排隊(duì),所以我們還需要一個(gè)隊(duì)列。

最后,這些線程排隊(duì)的時(shí)候干嘛呢?它們不能再繼續(xù)執(zhí)行自己的程序,那就只能阻塞了,阻塞完了當(dāng)輪到這個(gè)線程的時(shí)候還要喚醒,所以我們還需要Unsfae這個(gè)類來阻塞(park)和喚醒(unpark)線程。

基于以上四點(diǎn),我們需要的神器大致有:一個(gè)變量、一個(gè)隊(duì)列、執(zhí)行CAS/park/unpark的Unsafe類。

大概的流程圖如下圖所示:

關(guān)于Unsafe類的相關(guān)講解請(qǐng)參考彤哥之前發(fā)的文章:

【死磕 java魔法類之Unsafe解析】

解決

一個(gè)變量

這個(gè)變量只支持同時(shí)只有一個(gè)線程能把它修改為1,所以它修改完了一定要讓其它線程可見,因此,這個(gè)變量需要使用volatile來修飾。

private

CAS

這個(gè)變量的修改必須是原子操作,所以我們需要CAS更新它,我們這里使用Unsafe來直接CAS更新int類型的state。

當(dāng)然,這個(gè)變量如果直接使用AtomicInteger也是可以的,不過,既然我們學(xué)習(xí)了更底層的Unsafe類那就應(yīng)該用(浪)起來。

private

一個(gè)隊(duì)列

隊(duì)列的實(shí)現(xiàn)有很多,數(shù)組、鏈表都可以,我們這里采用鏈表,畢竟鏈表實(shí)現(xiàn)隊(duì)列相對(duì)簡(jiǎn)單一些,不用考慮擴(kuò)容等問題。

這個(gè)隊(duì)列的操作很有特點(diǎn):

放元素的時(shí)候都是放到尾部,且可能是多個(gè)線程一起放,所以對(duì)尾部的操作要CAS更新;

喚醒一個(gè)元素的時(shí)候從頭部開始,但同時(shí)只有一個(gè)線程在操作,即獲得了鎖的那個(gè)線程,所以對(duì)頭部的操作不需要CAS去更新。

private

這個(gè)隊(duì)列很簡(jiǎn)單,存儲(chǔ)的元素是線程,需要有指向下一個(gè)待喚醒的節(jié)點(diǎn),前一個(gè)節(jié)點(diǎn)可有可無,但是沒有實(shí)現(xiàn)起來很困難,不信學(xué)完這篇文章你試試。

加鎖

public

(1)嘗試獲取鎖,成功了就直接返回;

(2)未獲取到鎖,就進(jìn)入隊(duì)列排隊(duì);

(3)入隊(duì)之后,再次嘗試獲取鎖;

(4)如果不成功,就阻塞;

(5)如果成功了,就把頭節(jié)點(diǎn)后移一位,并清空當(dāng)前節(jié)點(diǎn)的內(nèi)容,且與上一個(gè)節(jié)點(diǎn)斷絕關(guān)系;

(6)加鎖結(jié)束;

解鎖

// 解鎖

(1)把state改成0,這里不需要CAS更新,因?yàn)楝F(xiàn)在還在加鎖中,只有一個(gè)線程去更新,在這句之后就釋放了鎖;

(2)如果有下一個(gè)節(jié)點(diǎn)就喚醒它;

(3)喚醒之后就會(huì)接著走上面lock()方法的while循環(huán)再去嘗試獲取鎖;

(4)喚醒的線程不是百分之百能獲取到鎖的,因?yàn)檫@里state更新成0的時(shí)候就解鎖了,之后可能就有線程去嘗試加鎖了。

測(cè)試

上面完整的鎖的實(shí)現(xiàn)就完了,是不是很簡(jiǎn)單,但是它是不是真的可靠呢,敢不敢來試試?!

直接上測(cè)試代碼:

private

運(yùn)行這段代碼的結(jié)果是總是打印出10000000(一千萬),說明我們的鎖是正確的、可靠的、完美的。

總結(jié)

(1)自己動(dòng)手寫一個(gè)鎖需要做準(zhǔn)備:一個(gè)變量、一個(gè)隊(duì)列、Unsafe類。

(2)原子更新變量為1說明獲得鎖成功;

(3)原子更新變量為1失敗說明獲得鎖失敗,進(jìn)入隊(duì)列排隊(duì);

(4)更新隊(duì)列尾節(jié)點(diǎn)的時(shí)候是多線程競(jìng)爭(zhēng)的,所以要使用原子更新;

(5)更新隊(duì)列頭節(jié)點(diǎn)的時(shí)候只有一個(gè)線程,不存在競(jìng)爭(zhēng),所以不需要使用原子更新;

(6)隊(duì)列節(jié)點(diǎn)中的前一個(gè)節(jié)點(diǎn)prev的使用很巧妙,沒有它將很難實(shí)現(xiàn)一個(gè)鎖,只有寫過的人才明白,不信你試試^^

彩蛋

(1)我們實(shí)現(xiàn)的鎖支持可重入嗎?

答:不可重入,因?yàn)槲覀兠看沃话裺tate更新為1。如果要支持可重入也很簡(jiǎn)單,獲取鎖時(shí)檢測(cè)當(dāng)前鎖是不是當(dāng)前線程占有著,如果是就把state的值加1,釋放鎖時(shí)每次減1即可,減為0時(shí)表示鎖已釋放。

(2)我們實(shí)現(xiàn)的鎖是公平鎖還是非公平鎖?

答:非公平鎖,因?yàn)楂@取鎖的時(shí)候我們先嘗試了一次,這里并不是嚴(yán)格的排隊(duì),所以是非公平鎖。

注:下一章我們將開始分析傳說中的AQS,這章是基礎(chǔ),請(qǐng)各位老鐵務(wù)必搞明白。

推薦閱讀

  • 死磕 java魔法類之Unsafe解析
  • 死磕 java同步系列之JMM(Java Memory Model)
  • 死磕 java同步系列之volatile解析
  • 死磕 java同步系列之synchronized解析

  • 歡迎關(guān)注我的公眾號(hào)“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。

    總結(jié)

    以上是生活随笔為你收集整理的java 同步锁_死磕 java同步系列之自己动手写一个锁Lock的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。