點(diǎn)贊模塊中合并數(shù)據(jù)部分的實(shí)現(xiàn)

昨天我們談了一下如何設(shè)計(jì)點(diǎn)贊模塊,最后給出了優(yōu)化這個(gè)模塊的方案.

我們提到,可以通過合并收到的數(shù)據(jù),來進(jìn)行優(yōu)化.而如何合并數(shù)據(jù),就成了一個(gè)尤為重要的問題.

針對(duì)這個(gè)問題,我們也給出了幾種解決方案,一種是重寫Tomcat,通過Tomcat進(jìn)行攔截并定時(shí)處理,但是這種方案,實(shí)現(xiàn)起來難度有些大,第二種是通過服務(wù)器在處理請(qǐng)求時(shí),是為其新建一個(gè)線程來處理的原理,來實(shí)現(xiàn),這種方案相對(duì)較簡單一些,第三種是通過消息隊(duì)列來實(shí)現(xiàn),這種方案挺復(fù)雜,但是難度不高.

今天我們就采用第二種方案來實(shí)現(xiàn).實(shí)現(xiàn)起來也是一波三折.且聽我慢慢道來.

我們打算這樣實(shí)現(xiàn):

  • 創(chuàng)建一個(gè)緩沖區(qū),讓所有線程共享,然后將一分鐘內(nèi)收到的數(shù)據(jù),全部保存到這個(gè)變量中,并給前臺(tái)返回一個(gè)成功的標(biāo)記.
  • 過了一分鐘后,當(dāng)收到請(qǐng)求時(shí),先將緩沖區(qū)中的數(shù)據(jù),全部保存到數(shù)據(jù)庫中,然后將這個(gè)緩沖區(qū)清空,并再次寫入下個(gè)一分鐘之內(nèi)收到的數(shù)據(jù).

過程就是這么簡單.但是我們需要注意一些問題:

  • 如果在我們將數(shù)據(jù)保存到數(shù)據(jù)庫之后,清空緩沖區(qū)之前的這個(gè)時(shí)間段中,還有線程在向這個(gè)緩沖區(qū)寫入數(shù)據(jù),那么這些新寫入的數(shù)據(jù)沒有被保存就會(huì)被清除.所以我們需要先等待將之前收到的請(qǐng)求寫入到緩沖區(qū)結(jié)束后,阻塞后來收到的請(qǐng)求,再將數(shù)據(jù)保存到數(shù)據(jù)庫中,并清空緩沖區(qū).

  • 如果有多個(gè)線程執(zhí)行將數(shù)據(jù)保存到數(shù)據(jù)庫,并清空緩沖區(qū)的操作怎么辦?

我們先來看第一版的源代碼:

在第一版中,我們使用ReentrantLock來實(shí)現(xiàn)線程之間的同步.

我們先判斷此請(qǐng)求到來的時(shí)間與上次保存數(shù)據(jù)到數(shù)據(jù)庫的時(shí)間差,是否大于一分鐘.也就是判斷是否過去了一分鐘.如果是這樣,就獲取到ReentrantLock,并在有鎖的期間,將數(shù)據(jù)保存到數(shù)據(jù)庫中,然后重置保存數(shù)據(jù)到數(shù)據(jù)庫的時(shí)間點(diǎn),并清空保存一分鐘之內(nèi)收到的全部數(shù)據(jù)的緩沖區(qū).lastAggegate這個(gè)變量是AtomicLong類型的,這是一個(gè)線程安全的Long類型.likeData這個(gè)緩沖區(qū),是ConcurrentHashMap類型的,這是一個(gè)線程安全的HashMap.

我們?cè)谧鐾晟厦娴墓ぷ髦?釋放掉ReentrantLock.

下面的那塊代碼中,我們判斷是否已經(jīng)有線程占有ReentrantLock了,如果是,我們就一直循環(huán),等待它釋放.實(shí)際上就是起到了阻塞一分鐘之后收到的請(qǐng)求寫數(shù)據(jù)的作用.然后執(zhí)行后面的步驟.在后面,我們將收到的數(shù)據(jù),保存或者更新到likeData這個(gè)緩沖區(qū)中.

這里我們拿兩個(gè)同時(shí)到來的請(qǐng)求A和B,來分析一下上面的代碼:

第一種情況,先假設(shè)A和B都是在一分鐘之內(nèi)到來的,則會(huì)直接執(zhí)行下面的代碼塊.因?yàn)榇藭r(shí)ReentrantLock并沒有被任意一個(gè)線程占有,所以這兩個(gè)線程A和B,會(huì)并發(fā)更新likeData這個(gè)緩存區(qū).這個(gè)沒有什么問題.

第二種情況,假設(shè)A和B都是一分鐘之后到來的,先假設(shè)A先判斷是否過去一分鐘,并判斷為true,然后在下面獲得鎖,重置時(shí)間點(diǎn),這時(shí),B在判斷是否過去了一分鐘,判斷為false,然后B執(zhí)行下面的代碼塊,而因?yàn)?strong>ReentrantLock已經(jīng)被A獲取,所以它只能在while循環(huán)中一直等待.當(dāng)A清空likeData這個(gè)緩沖區(qū)并釋放ReentrantLock之后,B才得以和A同時(shí)并行的將數(shù)據(jù)寫入到likeData這個(gè)緩沖區(qū)中.如果單看這兩個(gè)線程,這個(gè)過程也沒有什么問題.結(jié)果也是正確的.可是,在A清空likeData這個(gè)緩沖區(qū)的時(shí)候,在高并發(fā)的情況下,很可能有一分鐘之內(nèi)到來的請(qǐng)求C,正在向緩沖區(qū)中寫入數(shù)據(jù)!!這樣,請(qǐng)求C的數(shù)據(jù),就被悄無聲息的刪除了.

第三種情況,假設(shè)A和B都是一分鐘之后到來的,它倆又同時(shí)判斷是否過去了一分鐘,并同時(shí)判斷為true,然后它們同時(shí)請(qǐng)求鎖,A或B其中一個(gè)獲得了ReentrantLock,然后執(zhí)行后面的流程.這個(gè)沒有什么好解釋的.同樣,它也有第二種情況中我們說的那種特點(diǎn),數(shù)據(jù)很可能被悄無聲息的刪除了.同時(shí),它還有一種特點(diǎn),就是在高并發(fā)情況下,假設(shè)有一百個(gè)線程是串行著請(qǐng)求鎖,即第一個(gè)線程釋放了鎖,第二個(gè)線程才請(qǐng)求鎖,等第二個(gè)線程釋放了鎖之后,第三個(gè)線程才請(qǐng)求鎖,依次類推.我們可以看到,這樣不僅會(huì)使意外清除數(shù)據(jù)的情況更加嚴(yán)重,還會(huì)有性能問題.我們需要執(zhí)行獲得一百次鎖,執(zhí)行一百次鎖內(nèi)需要執(zhí)行的操作.

第四種情況,假設(shè)A是一分鐘之內(nèi)到來的,B是一分鐘之后到來的,就可能出現(xiàn)我們上面第二種情況中,所說的那種意外.

這幾種情況里面,意外清空數(shù)據(jù)的情況最難處理,因?yàn)槲覀儫o法做到讓鎖內(nèi)需要執(zhí)行的一系列操作,讓其等到一分鐘之內(nèi)到來的請(qǐng)求都把數(shù)據(jù)寫入到likeData這個(gè)緩沖區(qū)之內(nèi),再執(zhí)行.

而多個(gè)線程同時(shí)判斷是否過去一分鐘,并判斷為true這種情況,我們可以通過將判斷及執(zhí)行所內(nèi)的操作放到**synchronized **塊中,來解決.

其實(shí)意外清空數(shù)據(jù)的這種情況,我們可以通過ReadWriteLock或者StampedLock來解決.我們之前介紹過這兩種鎖.

這兩種鎖為何能夠解決意外清空數(shù)據(jù)的情況呢?

各位應(yīng)該都知道,ReadWriteLock的規(guī)則,即:

  • 如果沒有線程持有寫鎖,那么可以有任意多個(gè)線程同時(shí)持有讀鎖,來讀數(shù)據(jù),因?yàn)樽x操作肯定是線程安全的.

  • 寫鎖最多只能被一個(gè)線程同時(shí)占有.

  • 不能同時(shí)占有讀鎖和寫鎖.如果線程A占有讀鎖,而線程B請(qǐng)求寫鎖,那么B必須等待A先釋放讀鎖,才能占有寫鎖.同樣,如果線程A占有寫鎖,而線程B請(qǐng)求讀鎖,那么B必須等待A先釋放寫鎖,才能占有讀鎖.

這個(gè)規(guī)則是否跟我們這里的需求很相似呢?

于是,我們寫出了第二版的代碼:

這里我們將判斷是否已經(jīng)過去了一分鐘,以及需要執(zhí)行的對(duì)應(yīng)的操作,都放到了synchronized同步代碼塊中,這樣,當(dāng)執(zhí)行這個(gè)代碼塊的時(shí)候,因?yàn)槭谴胁僮?所以同一時(shí)間只能有一個(gè)線程能夠獲得寫鎖,并執(zhí)行相應(yīng)的操作.

而可以并行執(zhí)行的將數(shù)據(jù)寫入緩沖區(qū)的操作,我們給其加一個(gè)讀鎖,讓其并行執(zhí)行.

這里我們可以看到,當(dāng)一個(gè)線程A判斷已經(jīng)過去一分鐘,并要將數(shù)據(jù)寫入到數(shù)據(jù)庫時(shí),需要先獲取寫鎖,而要占有寫鎖,必須沒有線程持有讀鎖.即之前的所有請(qǐng)求已經(jīng)將數(shù)據(jù)都寫入了likeData這個(gè)緩沖區(qū)之后,讀鎖都釋放了之后,線程A才能占有寫鎖并執(zhí)行相應(yīng)的操作.這就解決了數(shù)據(jù)被意外清除的問題.

同樣,因?yàn)橐@得讀鎖來將數(shù)據(jù)寫入到緩沖區(qū)時(shí),必須先等待寫鎖的釋放,也就相當(dāng)于阻塞了之后到來的請(qǐng)求的寫數(shù)據(jù)操作,防止在獲得寫鎖并執(zhí)行操作的這段時(shí)間中,到來的請(qǐng)求意外的向緩沖區(qū)中寫入數(shù)據(jù)并最終被清空.

用一百個(gè)線程,各發(fā)送了九次請(qǐng)求,沒有發(fā)現(xiàn)問題.請(qǐng)求中的全部數(shù)據(jù),可以被正確的保存到數(shù)據(jù)庫中.

在上面我們使用循環(huán)來獲取讀鎖和寫鎖,其實(shí)還有更好的寫法,就是使用上面提到過的StampedLock.因?yàn)?strong>ReadWriteLock的tryLock()方法是立即返回的,所以我們需要通過while循環(huán),不斷地測試是否能夠獲得鎖.即使可以為其設(shè)置超時(shí)時(shí)間,也是極不方便的.如果設(shè)置的過小,我們無法保證在這個(gè)超時(shí)時(shí)間之內(nèi),會(huì)獲得鎖,如果設(shè)置的過大,又浪費(fèi)時(shí)間,降低了效率.而StampedLock中的獲得鎖的方法,是會(huì)阻塞當(dāng)前線程的.也就是說,如果獲取不到鎖,就會(huì)阻塞當(dāng)前線程,一直到獲取到.這種方式其實(shí)更好一些,減少了上面因?yàn)?strong>while循環(huán)中CPU空轉(zhuǎn)造成的資源浪費(fèi).

還有一種更簡單的方案,就是將全部的操作,都放在synchronized代碼塊中.這個(gè)應(yīng)該也很好理解.這里不再詳細(xì)敘述這種方案.

但是,使用這種方案,有一個(gè)致命缺陷,就是性能的問題.我們向緩沖區(qū)寫數(shù)據(jù)的操作是可以并行的,如果全都放在synchronized里面,就只能是串行的,那全部的請(qǐng)求都得一個(gè)個(gè)的串行處理,對(duì)性能是極大的消耗.

而我們使用讀寫鎖的方案,只是將判斷以及寫數(shù)據(jù)庫的操作放入到synchronized塊中,雖然是串行,但是相當(dāng)輕量級(jí).大多數(shù)情況下,實(shí)際上只有判斷這條語句是需要并行執(zhí)行的,匯編指令也就是三條.

上面的讀寫鎖的代碼中,synchronizedexpression中,我們用的是一個(gè)用final修飾的,Integer類型的變量.它是不可變的.synchronized會(huì)取得** expression中的對(duì)象的monitor,將其當(dāng)做互斥條件,一個(gè)對(duì)象只有一個(gè)monitor與之對(duì)應(yīng),如果我們拿一個(gè)不是final的可變的對(duì)象來做expression,那么很可能并沒有被正確的同步,得到的結(jié)果也是不正確的.當(dāng)我用likeData這個(gè)ConcurrentHashMap對(duì)象時(shí),以及lastAggegate**這個(gè)變量時(shí),會(huì)出現(xiàn)錯(cuò)誤的結(jié)果.

上面我們的緩沖區(qū)用的是ConcurrentHashMap這個(gè)容器,這個(gè)容器在讀多寫少時(shí),性能很好,而我們現(xiàn)在是寫多讀少,跟它相反,雖然還沒有遇到什么性能問題,但是這里應(yīng)該選擇一個(gè)合適的適合寫多讀少的線程安全的Map.不知道有沒有.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末匪凉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子趾徽,更是在濱河造成了極大的恐慌撞羽,老刑警劉巖蝙斜,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件简软,死亡現(xiàn)場離奇詭異射众,居然都是意外死亡碟摆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門叨橱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來典蜕,“玉大人,你說我怎么就攤上這事罗洗∮涮颍” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵伙菜,是天一觀的道長轩缤。 經(jīng)常有香客問我,道長贩绕,這世上最難降的妖魔是什么火的? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮淑倾,結(jié)果婚禮上馏鹤,老公的妹妹穿的比我還像新娘。我一直安慰自己娇哆,他們只是感情好湃累,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著迂尝,像睡著了一般脱茉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上垄开,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天琴许,我揣著相機(jī)與錄音,去河邊找鬼溉躲。 笑死榜田,一個(gè)胖子當(dāng)著我的面吹牛益兄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播箭券,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼邪意,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了您旁?” 一聲冷哼從身側(cè)響起帕胆,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎废亭,沒想到半個(gè)月后国章,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡豆村,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年液兽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掌动。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡四啰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粗恢,到底是詐尸還是另有隱情柑晒,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布适滓,位于F島的核電站敦迄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏凭迹。R本人自食惡果不足惜罚屋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嗅绸。 院中可真熱鬧脾猛,春花似錦、人聲如沸鱼鸠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚀狰。三九已至愉昆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間麻蹋,已是汗流浹背跛溉。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芳室。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓专肪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親堪侯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嚎尤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Java8張圖 11、字符串不變性 12伍宦、equals()方法芽死、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,701評(píng)論 0 11
  • 從三月份找實(shí)習(xí)到現(xiàn)在雹拄,面了一些公司收奔,掛了不少,但最終還是拿到小米滓玖、百度、阿里质蕉、京東势篡、新浪、CVTE模暗、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,240評(píng)論 11 349
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法禁悠,類相關(guān)的語法,內(nèi)部類的語法兑宇,繼承相關(guān)的語法碍侦,異常的語法,線程的語...
    子非魚_t_閱讀 31,623評(píng)論 18 399
  • 關(guān)于在北京大學(xué)舉辦“諾貝爾獎(jiǎng)與文化軟實(shí)力”論壇的通知 北京大學(xué)校友屠呦呦女士榮摘2015年度諾貝爾生理學(xué)或醫(yī)學(xué)獎(jiǎng)桂...
    知識(shí)分子閱讀 413評(píng)論 0 0
  • 2016.8.16#是云開霧散的太陽隶糕,武志紅的《成為自己》講到所有的孩子都是肆無忌憚的伸出觸角瓷产,強(qiáng)壯的媽媽接納著他...
    愛花的小巫閱讀 533評(píng)論 0 0