????11-多線程(多線程-同步函數(shù))
????????銀行類:
? ? ? ? 儲戶類:
? ? ? ? 主函數(shù)中開兩個線程:
????????運(yùn)行結(jié)果:
? ? ? ? 但是多運(yùn)行幾次,發(fā)現(xiàn)順序不太對:
? ? ? ? 如何找問題坝疼?
? ? ? ? 1涌矢,明確哪些代碼是多線程運(yùn)行代碼按摘。
? ? ? ? 2,明確共享數(shù)據(jù)濒旦。
? ? ? ? 3株旷,明確多線程運(yùn)行代碼中哪些語句是操作共享數(shù)據(jù)的。
? ? ? ? 我們就來一條一條找:
? ? ? ? 1,哪些代碼是多線程運(yùn)行代碼:
? ? ? ? 2晾剖,共享數(shù)據(jù)锉矢。
? ? ? ? 3,多線程運(yùn)行代碼中哪些語句是操作共享數(shù)據(jù)的:
? ? ? ? run方法中只有一句應(yīng)該不是問題所在齿尽,add方法中有兩句沽损,我們來分析一下:
? ? ? ? 第一個線程進(jìn)入add,給sum加了n循头,還沒來的及打印绵估,下一個就占用了cpu,也進(jìn)入了add卡骂,做給sum加n的動作国裳,它加完打印完之后,剛剛那個沒來得及打印的線程才獲得了cpu的執(zhí)行權(quán)全跨,繼續(xù)打印缝左,這個時候,就會導(dǎo)致打印的順序不對浓若。
? ? ? ? 為了驗(yàn)證渺杉,我們sleep一下:
? ? ? ? 編譯運(yùn)行:
? ? ? ? 天啦嚕,問題好嚴(yán)重挪钓!
? ? ? ? 我們要解決問題少办!
? ? ? ? 編譯運(yùn)行:
? ? ? ? 問題解決啦。
? ? ? ? 那我們可以把這部分鎖著嗎诵原?
? ? ? ? 也不是不可以,但又一個問題挽放,就是如果李四進(jìn)來了绍赛,得等到他把300塊錢全存完,張三才能進(jìn)來辑畦。
? ? ? ? 所以吗蚌,哪些代碼該同步,哪些代碼不該同步纯出,一定要搞清楚蚯妇。
? ? ? ? 怎么搞清楚呢?就剛剛那三個步驟暂筝。
? ???????如何找問題箩言?
? ??????1,明確哪些代碼是多線程運(yùn)行代碼焕襟。
? ??????2陨收,明確共享數(shù)據(jù)。
? ? ? ? 3,明確多線程運(yùn)行代碼中哪些語句是操作共享數(shù)據(jù)的务漩。
? ? ? ? 接下來要講新知識點(diǎn)啦拄衰。
? ? ? ? 我們思考一下:
? ? ? ? 同步代碼塊是用來封裝代碼的,函數(shù)也是用來封裝代碼的饵骨,那它們有什么不同呢翘悉?
? ? ? ? 同步代碼塊相對比函數(shù),是擁有了同步性居触,那我們也讓函數(shù)具有同步性妖混,這個點(diǎn)子怎么樣?
? ? ? ? 其實(shí)超級簡單饼煞,把synchronized關(guān)鍵作為修飾符放在函數(shù)上就OK啦:
? ? ? ? 總結(jié)一下:
? ? ? ? 同步有兩種表現(xiàn)形式源葫,第一個是同步代碼塊,第二個是同步函數(shù)砖瞧。
? ??12-多線程(多線程-同步函數(shù)的鎖是this)
? ? ? ? 我們現(xiàn)在把之前賣票的程序也改成同步函數(shù)玩一下~
? ? ? ? 編譯運(yùn)行:
? ? ? ? 我們發(fā)現(xiàn)息堂,一直是0線程在做這件事情,1块促、2荣堰、3線程根本沒啟動。
? ? ? ? 我們分析一下竭翠,0進(jìn)來了振坚,1、2斋扰、3被鎖外面了渡八,而0一進(jìn)來就一直循環(huán)直到結(jié)束,1传货、2屎鳍、3根本沒有機(jī)會:
? ? ? ? 所以這么做不可以,因?yàn)闆]有搞清楚哪些需要同步问裕,哪些不需要逮壁。
? ? ? ? 那該怎么寫呢?單獨(dú)寫一個函數(shù):
? ? ? ? 這個問題就解決啦粮宛。
? ? ? ? 但是另一個問題來了窥淆,我們這里已經(jīng)沒有object對象了,那同步函數(shù)用的是哪個鎖巍杈?
? ? ? ? show方法是不是需要被對象調(diào)用的呀忧饭?那同步函數(shù)能夠用到的鎖,就是調(diào)用它的對象:this秉氧。
? ? ? ? 因?yàn)楹瘮?shù)需要被對象調(diào)用眷昆,那么函數(shù)都有一個所屬對象引用,就是this。
? ? ? ? 所以同步函數(shù)使用的鎖是this亚斋。
? ? ? ? 我們現(xiàn)在來驗(yàn)證一下:
? ? ? ? 使用兩個線程來賣票作媚。一個線程在同步代碼塊中,一個線程在同步函數(shù)中帅刊,都在執(zhí)行賣票動作纸泡。
? ? ? ? 如果要是同步的話,就不會出現(xiàn)錯誤赖瞒。
? ? ? ? 稍微修改一下代碼:
? ? ? ? 同步代碼塊:
? ? ? ? 同步函數(shù):
? ? ? ? 主函數(shù)總開啟兩個線程:
? ? ? ? 編譯運(yùn)行:
? ? ? ? 0線程也有女揭,1線程也有,可是為什么全都是show栏饮?code沒打印吧兔。
? ? ? ? 分析一下:現(xiàn)在有三個線程:主線程,0線程袍嬉,1線程境蔼。
? ? ? ? 主線程啟動后,創(chuàng)建了兩個線程對象伺通,然后開啟了0線程箍土,開啟完之后鸽捻,0線程會立刻執(zhí)行嗎褥傍?不一定,它先是處于臨時狀態(tài)懈凹,有了資格但是還沒有執(zhí)行權(quán)弓柱,因?yàn)檫@個時候是主線程持有執(zhí)行權(quán)沟堡。主線程有可能瞬間把這幾句話執(zhí)行完:
? ? ? ? 瞬間幾句話執(zhí)行完后,flag值為false矢空。
? ? ? ? 主線程執(zhí)行完t2.start()之后弦叶,就結(jié)束了。
? ? ? ? 現(xiàn)在就剩兩個線程:0線程和1線程妇多,它們開始運(yùn)行了。
? ? ? ? 因?yàn)閒lag值為false燕侠,所以0線程和1線程都去show中執(zhí)行了者祖。
? ? ? ? 那該怎么解決呢?
? ? ? ? 想讓主線程執(zhí)行完t1.start()之后先停一下绢彤,讓0線程運(yùn)行一會兒:
? ? ? ? 注意七问,在主線程睡眠的這10ms內(nèi),能運(yùn)行的線程只有0線程茫舶,這個時候他就去執(zhí)行同步代碼塊了械巡。
? ? ? ? 過了10ms,主線程醒了。醒完以后讥耗,它繼續(xù)往下執(zhí)行有勾,將flag置為假,開啟了1線程古程,1線程就去執(zhí)行同步函數(shù)show了蔼卡。
? ? ? ? 此時0線程和1線程同時運(yùn)行。
? ? ? ? 編譯運(yùn)行:
? ? ? ? 搞定挣磨!
? ? ? ? 但是這里出現(xiàn)錯票了:
? ? ? ? 因?yàn)槠睌?shù)只能到1雇逞,出現(xiàn)賣0號票就錯了。
? ? ? ? 所以不安全茁裙。
? ? ? ? 可是明明加了同步塘砸,怎么就能不安全呢?
? ? ? ? 加了同步還沒解決晤锥,肯定是兩個前提中至少有一個沒滿足掉蔬。
? ? ? ? 前提:
? ? ? ? 1,必須是兩個及兩個以上的線程查近。滿足了眉踱。
? ? ? ? 2,用的是同一個鎖霜威。不是谈喳。0線程用的obj鎖,1線程用的this鎖戈泼。
? ? ? ? 我們將同步代碼塊也改成this鎖試一試:?
? ? ? ? 編譯運(yùn)行:
? ? ? ? 安全啦婿禽!
? ? ? ? 通過這個小程序,我們也側(cè)面驗(yàn)證了同步函數(shù)使用的鎖是this大猛,一箭雙雕嘻嘻~
????13-多線程(多線程-靜態(tài)同步函數(shù)的鎖是Class對象)
? ? ? ? 我們將show用static修飾:
? ? ? ? tick也用static修飾:
? ? ? ? 編譯運(yùn)行:
? ? ? ? 我們發(fā)現(xiàn)扭倾,又出現(xiàn)0號票了,怎么會醬紫挽绩!
? ? ? ? 因此膛壹,靜態(tài)的同步函數(shù),用的鎖肯定不是this了(因?yàn)殪o態(tài)方法中也不可以定義this)唉堪,那么它用的鎖是什么呢模聋?
? ? ? ? 靜態(tài)進(jìn)內(nèi)存時,內(nèi)存中沒有本類對象唠亚,到那時一定有該類對應(yīng)的字節(jié)碼文件對象:類名.class链方,該對象的類型是Class。
? ? ? ? 所以將同步代碼塊的鎖修改成Ticket.class試一下:
? ? ? ? 編譯運(yùn)行:
? ? ? ? 安全啦灶搜!
? ? ? ? 因此祟蚀,靜態(tài)的同步方法工窍,使用的鎖是該方法所在類的字節(jié)碼文件對象:類名.class。
? ??14-多線程(多線程-單例設(shè)計(jì)模式-懶漢式)
? ? ? ? 講完了這些前酿,我們回過頭看一下之前講過的單例設(shè)計(jì)模式——懶漢式患雏。因?yàn)檫@個小知識點(diǎn)只有我們學(xué)完同步之后才能講~? ?
? ? ? ? 我們回顧一下單例設(shè)計(jì)模式:
? ? ? ? 餓漢式:
? ? ? ? 懶漢式:
? ? ? ? 這里有一個問題,如果多個線程并發(fā)訪問這個getInstance薪者,是不是有多條語句在操作共享數(shù)據(jù)s纵苛?(一條在判斷,一條在賦值言津。)
? ? ? ? 加上synchronized就搞定了:
? ? ? ? 但是還有個問題攻人,如果有很多個線程都要訪問getInstance,是不是每個線程都要訪問這個鎖悬槽?所以懶漢式加了同步會比較低效怀吻。
? ? ? ? 怎么辦呢?
? ? ? ? 這個時候我們可以這樣干:
? ? ? ? 但是這樣寫和那樣寫不是沒啥區(qū)別嘛初婆,那再加一句:
? ? ? ? 我們分析一下:
? ? ? ? A一進(jìn)來蓬坡,滿足第一次s==null判斷,拿到鎖就進(jìn)來了磅叛,進(jìn)來之后A掛這里了:
? ? ? ? B一進(jìn)來屑咳,也滿足第一次s==null判斷,但是沒拿到鎖弊琴。
? ? ? ? 這個時候A繼續(xù)執(zhí)行s=new Single();并且在執(zhí)行完之后就出去啦兆龙。
? ? ? ? 這時候B拿到了鎖,也進(jìn)來了敲董。進(jìn)行第二次s==null的判斷紫皇,顯然不滿足。所以return s腋寨。
? ? ? ? 這時C進(jìn)來了聪铺,進(jìn)行第一次s==null的判斷,不滿足萄窜。而且C以后進(jìn)來的線程铃剔,它們都不滿足第一次s==null的判斷,所以都不用再判斷這個鎖查刻。
? ? ? ? 這樣做番宁,減少了判斷鎖的次數(shù)。
? ? ? ? 所以用雙重判斷赖阻,減少了判斷鎖的次數(shù),稍微提高了懶漢式的效率踱蠢。
? ? ? ? 但是不管怎樣火欧,餓漢式只有一句話就可以解決的問題棋电,懶漢式都要用好幾句。所以開發(fā)中還是寫?zhàn)I漢式好一些苇侵。
? ? ? ? 之所以講這么詳細(xì)赶盔,是因?yàn)樵诿嬖囍薪?jīng)常會考到懶漢式。
????????比如餓漢式和懶漢式有什么不同榆浓?懶漢式的特點(diǎn)在于實(shí)例的延遲加載于未。
? ? ? ? 懶漢式的延遲加載有沒有問題?有陡鹃, 如果多線程訪問時會出現(xiàn)安全問題烘浦。?
? ? ? ? 怎么解決?用同步來解決萍鲸。用同步代碼塊或者同步函數(shù)都可以闷叉,但是稍微有點(diǎn)低效。用雙重判斷可以稍微解決一下效率問題脊阴。
? ? ? ? 加同步的時候使用的鎖是哪一個握侧?該類所處的字節(jié)碼對象。
? ? ? ? 所以嘿期,就這一個小問題可以考好多地方呢品擎。
? ? ? ? 這個代碼要會寫哦:
? ? ? ? 一般有可能會要求:請寫一個延遲加載的單例設(shè)計(jì)模式示例。就寫這個~
????15-多線程(多線程-死鎖)
? ? ? ? 剛剛講的this鎖和字節(jié)碼對象鎖結(jié)論記住就行啦备徐,代碼有點(diǎn)麻煩萄传,了解即可。但是單例設(shè)計(jì)模式的代碼一定要會哦坦喘。? ? ? ??
? ? ? ? 回到我們的進(jìn)度中來盲再。
? ? ? ? 同步還有一個弊端:死鎖。這是同步出現(xiàn)之后會產(chǎn)生的一個現(xiàn)象瓣铣。
? ? ? ? 何為死鎖呢答朋?
? ? ? ? 你持有一個鎖,我也持有一個鎖棠笑,我不放我的鎖要到你那里面去運(yùn)行梦碗,而你不放你的鎖要到我這里面去運(yùn)行。誰都不放蓖救,就導(dǎo)致了死鎖洪规。
? ? ? ? 死鎖一產(chǎn)生,程序就掛那兒不動了循捺。
? ? ? ? 通常死鎖出現(xiàn)的原因是:同步中嵌套出現(xiàn)斩例,而鎖卻不同。
? ? ? ? 我們稍微修改一下代碼从橘,讓它出現(xiàn)這種情況:
? ? ? ? 編譯運(yùn)行念赶,卡這里不動了:
? ? ? ? ?我們分析一下:?
? ? ? ? 這個時候就鎖住了础钠。
? ? ? ? 注意這樣寫不是教你寫死鎖哦,是要你一定要避免死鎖~
? ? ? ? 再來一個稍微簡單的死鎖例子:
? ? ? ? Test類實(shí)現(xiàn)Runnable接口:
????????為了方便起見叉谜,單獨(dú)寫了一個類裝倆鎖(鎖是對象哦):
????????run方法中:
? ? ? ? 主函數(shù)中:
? ? ? ? 編譯運(yùn)行:
? ? ? ? 鎖住了旗吁。
? ? ? ? 分析原因:
? ? ? ? 當(dāng)然多運(yùn)行幾次偶爾也會出現(xiàn)和諧的情況。(但是這只是僥幸M>帧)
? ? ? ? 面試的時候很钓,有一道考題叫:請給我寫一個死鎖程序。這個考的就是對死鎖的理解董栽。能寫出來码倦,就說明對死鎖理解的差不多了,避免死鎖也不是問題了裆泳。
? ? ? ? 終于寫完啦叹洲,吃午餐去辣!想吃面面~