你知道如何用面向对象思想写好并发编程吗?
在工作中,我發(fā)現(xiàn)很多人在設(shè)計(jì)之初都是直接按照單線程的思路來(lái)寫(xiě)程序的,而忽略了本應(yīng)該重視的并發(fā)問(wèn)題;等上線后的某天,突然發(fā)現(xiàn)詭異的 Bug,再歷經(jīng)千辛萬(wàn)苦終于定位到問(wèn)題所在,卻發(fā)現(xiàn)對(duì)于如何解決已經(jīng)沒(méi)有了思路。
關(guān)于這個(gè)問(wèn)題,我覺(jué)得咱們今天很有必要好好聊聊“如何用面向?qū)ο笏枷雽?xiě)好并發(fā)程序”這個(gè)話題。
面向?qū)ο笏枷肱c并發(fā)編程有關(guān)系嗎?本來(lái)是沒(méi)關(guān)系的,它們分屬兩個(gè)不同的領(lǐng)域,但是在 Java 語(yǔ)言里,這兩個(gè)領(lǐng)域被無(wú)情地融合在一起了,好在融合的效果還是不錯(cuò)的:在 Java 語(yǔ)言里,面向?qū)ο笏枷肽軌蜃尣l(fā)編程變得更簡(jiǎn)單。
那如何才能用面向?qū)ο笏枷雽?xiě)好并發(fā)程序呢?結(jié)合我自己的工作經(jīng)驗(yàn)來(lái)看,我覺(jué)得你可以從封裝共享變量、識(shí)別共享變量間的約束條件和制定并發(fā)訪問(wèn)策略這三個(gè)方面下手。
一、封裝共享變量
并發(fā)程序,我們關(guān)注的一個(gè)核心問(wèn)題,不過(guò)是解決多線程同時(shí)訪問(wèn)共享變量的問(wèn)題。
面向?qū)ο笏枷肜锩嬗幸粋€(gè)很重要的特性是封裝,封裝的通俗解釋就是將屬性和實(shí)現(xiàn)細(xì)節(jié)封裝在對(duì)象內(nèi)部,外界對(duì)象只能通過(guò)目標(biāo)對(duì)象提供的公共方法來(lái)間接訪問(wèn)這些內(nèi)部屬性,這和門(mén)票管理模型匹配度相當(dāng)?shù)母?#xff0c;球場(chǎng)里的座位就是對(duì)象屬性,球場(chǎng)入口就是對(duì)象的公共方法。我們把共享變量作為對(duì)象的屬性,那對(duì)于共享變量的訪問(wèn)路徑就是對(duì)象的公共方法,所有入口都要安排檢票程序就相當(dāng)于我們前面提到的并發(fā)訪問(wèn)策略。
利用面向?qū)ο笏枷雽?xiě)并發(fā)程序的思路,其實(shí)就這么簡(jiǎn)單:將共享變量作為對(duì)象屬性封裝在內(nèi)部,對(duì)所有公共方法制定并發(fā)訪問(wèn)策略。 就拿很多統(tǒng)計(jì)程序都要用到計(jì)數(shù)器來(lái)說(shuō),下面的計(jì)數(shù)器程序共享變量只有一個(gè),就是 value,我們把它作為 Counter 類的屬性,并且將兩個(gè)公共方法 get() 和 addOne() 聲明為同步方法,這樣 Counter 類就成為一個(gè)線程安全的類了。
public class Counter {private long value;synchronized long get(){return value;}synchronized long addOne(){return ++value;} }當(dāng)然,實(shí)際工作中,很多的場(chǎng)景都不會(huì)像計(jì)數(shù)器這么簡(jiǎn)單,經(jīng)常要面臨的情況往往是有很多的共享變量,例如,信用卡賬戶有卡號(hào)、姓名、身份證、信用額度、已出賬單、未出賬單等很多共享變量。這么多的共享變量,如果每一個(gè)都考慮它的并發(fā)安全問(wèn)題,那我們就累死了。但其實(shí)仔細(xì)觀察,你會(huì)發(fā)現(xiàn),很多共享變量的值是不會(huì)變的,例如信用卡賬戶的卡號(hào)、姓名、身份證。對(duì)于這些不會(huì)發(fā)生變化的共享變量,建議你用 final 關(guān)鍵字來(lái)修飾。 這樣既能避免并發(fā)問(wèn)題,也能很明了地表明你的設(shè)計(jì)意圖,讓后面接手你程序的兄弟知道,你已經(jīng)考慮過(guò)這些共享變量的并發(fā)安全問(wèn)題了。
二、識(shí)別共享變量間的約束條件
識(shí)別共享變量間的約束條件非常重要。因?yàn)?strong>這些約束條件,決定了并發(fā)訪問(wèn)策略。 例如,庫(kù)存管理里面有個(gè)合理庫(kù)存的概念,庫(kù)存量不能太高,也不能太低,它有一個(gè)上限和一個(gè)下限。關(guān)于這些約束條件,我們可以用下面的程序來(lái)模擬一下。在類 SafeWM 中,聲明了兩個(gè)成員變量 upper 和 lower,分別代表庫(kù)存上限和庫(kù)存下限,這兩個(gè)變量用了 AtomicLong 這個(gè)原子類,原子類是線程安全的,所以這兩個(gè)成員變量的 set 方法就不需要同步了。
public class SafeWM {// 庫(kù)存上限private final AtomicLong upper = new AtomicLong(0);// 庫(kù)存下限private final AtomicLong lower = new AtomicLong(0);// 設(shè)置庫(kù)存上限void setUpper(long v){upper.set(v);}// 設(shè)置庫(kù)存下限void setLower(long v){lower.set(v);}// 省略其他業(yè)務(wù)代碼 }雖說(shuō)上面的代碼是沒(méi)有問(wèn)題的,但是忽視了一個(gè)約束條件,就是庫(kù)存下限要小于庫(kù)存上限,這個(gè)約束條件能夠直接加到上面的 set 方法上嗎?我們先直接加一下看看效果(如下面代碼所示)。我們?cè)?setUpper() 和 setLower() 中增加了參數(shù)校驗(yàn),這乍看上去好像是對(duì)的,但其實(shí)存在并發(fā)問(wèn)題,問(wèn)題在于存在競(jìng)態(tài)條件。這里我順便插一句,其實(shí)當(dāng)你看到代碼里出現(xiàn) if 語(yǔ)句的時(shí)候,就應(yīng)該立刻意識(shí)到可能存在競(jìng)態(tài)條件。
我們假設(shè)庫(kù)存的下限和上限分別是 (2,10),線程 A 調(diào)用 setUpper(5) 將上限設(shè)置為 5,線程 B 調(diào)用 setLower(7) 將下限設(shè)置為 7,如果線程 A 和線程 B 完全同時(shí)執(zhí)行,你會(huì)發(fā)現(xiàn)線程 A 能夠通過(guò)參數(shù)校驗(yàn),因?yàn)檫@個(gè)時(shí)候,下限還沒(méi)有被線程 B 設(shè)置,還是 2,而 5>2;線程 B 也能夠通過(guò)參數(shù)校驗(yàn),因?yàn)檫@個(gè)時(shí)候,上限還沒(méi)有被線程 A 設(shè)置,還是 10,而 7<10。當(dāng)線程 A 和線程 B 都通過(guò)參數(shù)校驗(yàn)后,就把庫(kù)存的下限和上限設(shè)置成 (7, 5) 了,顯然此時(shí)的結(jié)果是不符合庫(kù)存下限要小于庫(kù)存上限這個(gè)約束條件的。
public class SafeWM {// 庫(kù)存上限private final AtomicLong upper = new AtomicLong(0);// 庫(kù)存下限private final AtomicLong lower = new AtomicLong(0);// 設(shè)置庫(kù)存上限void setUpper(long v){// 檢查參數(shù)合法性if (v < lower.get()) {throw new IllegalArgumentException();}upper.set(v);}// 設(shè)置庫(kù)存下限void setLower(long v){// 檢查參數(shù)合法性if (v > upper.get()) {throw new IllegalArgumentException();}lower.set(v);}// 省略其他業(yè)務(wù)代碼 }在沒(méi)有識(shí)別出庫(kù)存下限要小于庫(kù)存上限這個(gè)約束條件之前,我們制定的并發(fā)訪問(wèn)策略是利用原子類,但是這個(gè)策略,完全不能保證庫(kù)存下限要小于庫(kù)存上限這個(gè)約束條件。所以說(shuō),在設(shè)計(jì)階段,我們一定要識(shí)別出所有共享變量之間的約束條件,如果約束條件識(shí)別不足,很可能導(dǎo)致制定的并發(fā)訪問(wèn)策略南轅北轍。
共享變量之間的約束條件,反映在代碼里,基本上都會(huì)有 if 語(yǔ)句,所以,一定要特別注意競(jìng)態(tài)條件。
三、制定并發(fā)訪問(wèn)策略
制定并發(fā)訪問(wèn)策略,是一個(gè)非常復(fù)雜的事情。應(yīng)該說(shuō)整個(gè)專欄都是在嘗試搞定它。不過(guò)從方案上來(lái)看,無(wú)外乎就是以下“三件事”。
接下來(lái)在咱們專欄的第二模塊我會(huì)仔細(xì)講解 Java 并發(fā)工具類以及他們的應(yīng)用場(chǎng)景,在第三模塊我還會(huì)講解并發(fā)編程的設(shè)計(jì)模式,這些都是和制定并發(fā)訪問(wèn)策略有關(guān)的。
除了這些方案之外,還有一些宏觀的原則需要你了解。這些宏觀原則,有助于你寫(xiě)出“健壯”的并發(fā)程序。這些原則主要有以下三條。
總結(jié)
寫(xiě)在最后
很多人感嘆“學(xué)習(xí)無(wú)用”,實(shí)際上之所以產(chǎn)生無(wú)用論,是因?yàn)樽约合胍呐c自己所學(xué)的匹配不上,這也就意味著自己學(xué)得遠(yuǎn)遠(yuǎn)不夠。無(wú)論是學(xué)習(xí)還是工作,都應(yīng)該有主動(dòng)性,所以如果擁有大廠夢(mèng),那么就要自己努力去實(shí)現(xiàn)它。
以上學(xué)習(xí)資料均免費(fèi)放送,最后祝愿各位身體健康,順利拿到心儀的offer!
由于文章的篇幅有限,所以這次的螞蟻金服和京東面試題答案整理在了PDF文檔里
資料獲取方式:點(diǎn)贊+評(píng)論我的文章,關(guān)注我,然后戳這里即可免費(fèi)領(lǐng)取
CuqNXO-1623614570590)]
[外鏈圖片轉(zhuǎn)存中…(img-dlpWA0LK-1623614570592)]
[外鏈圖片轉(zhuǎn)存中…(img-mswpUISq-1623614570593)]
總結(jié)
以上是生活随笔為你收集整理的你知道如何用面向对象思想写好并发编程吗?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 成都欢乐谷两岁多的孩子可以玩吗
- 下一篇: 你知道怎么在生产环境下部署tomcat吗