這是why哥的第 75 篇原創(chuàng)文章
從Dubbo的優(yōu)雅停機(jī)說(shuō)起
好吧,其實(shí)本文并不是講 Dubbo 的優(yōu)雅停機(jī)的割笙。
只是我在 Dubbo 停機(jī)方法 DubboShutdownHook 類(lèi)中权烧,看到了這樣的一段代碼:
很明顯眯亦,這個(gè)地方最關(guān)鍵的地方是紅框框起來(lái)的部分。
而這個(gè) addShutdownHook 其實(shí)是 JDK 的方法:
java.lang.Runtime#addShutdownHook
最終般码,把傳進(jìn)來(lái)的 hook 放到了 hooks 里面妻率。
你說(shuō) hooks 是這個(gè)什么玩意?
這個(gè) hooks 調(diào)用的是 put 方法板祝,里面放了一個(gè) key宫静,一個(gè) value。
盲猜也知道:這個(gè) hooks 肯定是一個(gè) Map券时。那么這么多 Map 具體是哪個(gè)呢孤里?
來(lái)看看答案:
說(shuō)真的,第一次看到這個(gè) IdentityHashMap 的時(shí)候橘洞,我都有點(diǎn)愣住了捌袜。
一時(shí)間居然想不起來(lái)這是個(gè)什么玩意了,只是覺(jué)得有點(diǎn)眼熟炸枣。
至于它是干啥的虏等,有啥特性,那就更是摸不清楚了适肠。
于是我去了解了一下霍衫,發(fā)現(xiàn)這玩意,有點(diǎn)意思迂猴。屬于學(xué)了基本沒(méi)啥卵用采盒,但如果你知道,偶爾會(huì)出奇制勝的東西闯传。
有啥不一樣
IdentityHashMap 也是 Map 家族中的一員亥鬓。只是他的存在感也太低了,很多人都不知道還有這么一個(gè)玩意息尺。
甚至感覺(jué)它是一個(gè)第三方包里面引進(jìn)的類(lèi)携兵,沒(méi)想到居然是一個(gè)親兒子。
說(shuō)到 Map 家族搂誉,大家最熟悉的也就是 HashMap 了徐紧。
那么這個(gè) IdentityHashMap 和 HashMap 有什么區(qū)別呢?
先上個(gè)代碼給大家看看:
先不說(shuō)后半部分輸出什么了炭懊。
前面的 hashMap 最終的輸出結(jié)果你肯定知道吧并级。
由于多次 new String("why") 出來(lái)的字符串對(duì)象的 hashCode 是一樣的。
所以侮腹,最終 hashMap 里面只會(huì)留下最后一個(gè)值嘲碧。
這個(gè)點(diǎn),之前的這《why哥悄悄的給你說(shuō)幾個(gè)HashCode的破事》篇文章中已經(jīng)講過(guò)了父阻。相信不需要我再次補(bǔ)充愈涩。
疑問(wèn)點(diǎn)是 identityHashMap 最終會(huì)輸出什么呢望抽?
來(lái),看看結(jié)果:
OMG履婉,什么鬼煤篙?identityHashMap 里面把三個(gè)值都存下來(lái)啦?這么神奇的嗎毁腿?怎么做到的辑奈?
先不去想它怎么實(shí)現(xiàn)的,我們就把它當(dāng)個(gè)黑盒使用狸棍。
那么它在給我們傳遞什么樣的信息身害?
我們可以存多個(gè)相同的 key 到 map 里面了。
比如這樣的:
我把前面的示例代碼的中的 String 換成 Person 對(duì)象草戈。
來(lái)塌鸯,你先告訴我,hashMap 里面放了幾個(gè)對(duì)象唐片?一個(gè)還是三個(gè)丙猬?
什么,一個(gè)费韭?
你出去茧球,你個(gè)假粉絲!你自己看看是幾個(gè):
之前的文章里面說(shuō)過(guò)了星持,hashMap 里面抢埋,如果我們要用對(duì)象當(dāng)做 key。我們應(yīng)該怎么辦督暂?
必揪垄!須!要逻翁! 重寫(xiě)對(duì)象的 hashCode 和 equals 方法饥努。
HashMap 才會(huì)是表現(xiàn)的和我們預(yù)期一樣。
所以八回,當(dāng)我們重寫(xiě)了對(duì)象的 hashCode 和 equals 方法后酷愧,運(yùn)行結(jié)果是這樣的:
這兩個(gè)容器的執(zhí)行結(jié)果,含義是不一樣的缠诅。
hashMap 只能看到 18 歲的 why溶浴。
identityHashMap 可以看到 16 到 18 歲的 why。
總之管引,你是否重寫(xiě)了對(duì)象的 hashCode 和 equals 方法士败,identityHashMap 都不關(guān)心。
那么 identityHashMap 是怎么實(shí)現(xiàn)這個(gè)效果的呢汉匙?
我們?nèi)ピ创a中尋找一下答案拱烁。
暢游源碼-PUT
在講源碼之前,我先把 identityHashMap 的存儲(chǔ)套路給你說(shuō)一下噩翠,你看源碼的時(shí)候就輕松多了戏自。
不管怎么它還是一個(gè) Map,那么必然就有對(duì)應(yīng)的 hash 方法伤锚。
對(duì)于 identityHashMap 而言擅笔,經(jīng)過(guò) hash 方法,計(jì)算出 key 的下標(biāo)為 2:
key 放好了屯援,然后 value 直接放到 i+1 的位置:
key 的下一個(gè)位置猛们,就是這個(gè) key 的 value 值。 這就是 identityHashMap 的存儲(chǔ)套路狞洋。它的數(shù)據(jù)結(jié)構(gòu)不是數(shù)組加鏈表弯淘,就完完全全是一個(gè)數(shù)組。?
記住這個(gè)套路吉懊,我們先從 put 方法的源碼入手:
java.util.IdentityHashMap#put
在標(biāo)號(hào)為 ① 的地方庐橙,就是 hash 方法,入?yún)⑹俏覀儌魅氲膶?duì)象和 table 的長(zhǎng)度借嗽。
table 是個(gè)什么玩意呢态鳖?
是一個(gè) Object 的數(shù)組。所以恶导,我們知道了 identityHashMap 的數(shù)據(jù)結(jié)構(gòu)它還是一個(gè)數(shù)組浆竭,而且看注釋?zhuān)哼@個(gè) table 的長(zhǎng)度必須是 2 的整數(shù)倍,也就是偶數(shù)惨寿。
那么數(shù)組的默認(rèn)長(zhǎng)度是多少呢:
是的邦泄,看起來(lái)是 32。
但是當(dāng)我對(duì)程序進(jìn)行調(diào)試的時(shí)候我發(fā)現(xiàn)缤沦,這個(gè) len 居然是 64:
可以看到這個(gè) table 數(shù)組里面什么東西都沒(méi)有虎韵,也就根本不存在觸發(fā)擴(kuò)容什么的。
為什么長(zhǎng)度是 64 呢缸废?說(shuō)好的 32 呢包蓝?
后來(lái)我在構(gòu)造方法中找到了答案:
臥槽,說(shuō)好的默認(rèn)容量 32企量,你初始化的時(shí)候直接翻倍了测萎?
這是什么行為?年輕人届巩,你這代碼硅瞧,不講武德啊恕汇!
但是你轉(zhuǎn)念一想腕唧。默認(rèn)容量 32 是指的 key 的?容量或辖。而一個(gè) key 對(duì)應(yīng)一個(gè) value。 key + value 總共不就是 64 的長(zhǎng)度嗎枣接?
好了颂暇,我們接著看 hash 方法的具體實(shí)現(xiàn):
hash 方法只有兩行。但是這兩行都非常的關(guān)鍵但惶。
先看第一個(gè) System.identityHashCode耳鸯,這個(gè)是什么東西?
看看 API 上的解釋?zhuān)?/p>
就是對(duì)于一個(gè)對(duì)象膀曾,不管你有沒(méi)有重寫(xiě) hashCode 方法县爬,該方法返回的值都是不會(huì)變化的。
看兩個(gè)示例代碼:
注意 Person 對(duì)象是沒(méi)有重寫(xiě) hashCode 方法的添谊。
程序的最終輸出結(jié)果是這樣的:
我們分成三個(gè)部分去看财喳,我們可以發(fā)現(xiàn)。
當(dāng)對(duì)象(Person)沒(méi)有重寫(xiě) hashCode 方法的時(shí)候斩狱,他們的 hashCode 和 identityHashCode 是一樣的纲缓。
即使對(duì)象(String)重寫(xiě)了 hashCode 方法,對(duì)于不同的對(duì)象喊废,hashCode 值是一樣的祝高,但是 identityHashCode 可能是不一樣的。
注意是“可能不一樣”污筷。因?yàn)?identityHashCode 的底層邏輯是基于一個(gè)偽隨機(jī)數(shù)生成的工闺。
這個(gè)特性特別有用,但是也別亂用瓣蛀。用錯(cuò)了陆蟆,就是一個(gè) bug。
比如在 identityHashMap 里面的使用就是一個(gè)正確的使用惋增。至于錯(cuò)誤的使用叠殷,我們稍后會(huì)講。
經(jīng)過(guò)前面的分析我們知道了:hash 方法中的第一行代碼诈皿,對(duì)于 new 出來(lái)的相同對(duì)象的不同實(shí)例林束,不管是否重寫(xiě) hashCode 方法,會(huì)產(chǎn)生不同的 identityHashCode稽亏。
可以說(shuō) System.identityHashCode 方法壶冒,是整個(gè) identityHashMap 的基石。
然后再看這一行代碼:
很多朋友第一眼看到位運(yùn)算截歉,心里就稍微有點(diǎn)抵觸胖腾。
別這樣,我?guī)惴治鲆幌拢芎?jiǎn)單的咸作。
首先锨阿,我前面畫(huà)圖示意了 identityHashMap 的存儲(chǔ)套路,說(shuō)了:key 的下一個(gè)位置就是這個(gè) key 的 value记罚。
那么 key 的位置一定要是一個(gè)偶數(shù)群井。
這一點(diǎn)能不能跟上?跟不上你就多想想再往下看毫胜。
而 hash 方法就是計(jì)算 key 的位置。
所以诬辈,該方法的返回值一定是一個(gè)偶數(shù)酵使。
這縝密的邏輯,是不是無(wú)懈可擊焙糟。
假設(shè) length 為 64 的話口渔,那么這一行代碼的目的是為了生成一個(gè) 0 到 63 之間的偶數(shù)。
0 到 63 之間的數(shù)穿撮,是 &(length-1) 保證的缺脉。這個(gè)沒(méi)啥說(shuō)的。
那么為什么一定會(huì)生成一個(gè)偶數(shù)呢悦穿?
h<<1 的最終結(jié)果肯定是一個(gè)偶數(shù)吧攻礼?
h<<8 的最終結(jié)果肯定也是一個(gè)偶數(shù)吧?
那么偶數(shù)減去偶數(shù)是一個(gè)什么數(shù)栗柒?
什么礁扮,你問(wèn)我會(huì)不會(huì)溢出?
你管它溢出不溢出瞬沦,就算它變成負(fù)數(shù)了太伊,變成 0 了,它也是一個(gè)偶數(shù)呀逛钻!
偶數(shù)的二進(jìn)制的最后一位是不是 0僚焦?
length-1 這個(gè)數(shù)的二進(jìn)制最后一位不是 0 就是 1,對(duì)不對(duì)曙痘?
0 & 上 0 或者 1芳悲,是不是還是 0?
那不就對(duì)了边坤。所以芭概,最終結(jié)果肯定是一個(gè)偶數(shù)的。
經(jīng)過(guò)前面的分析惩嘉,我們知道了標(biāo)號(hào)為 ① 的地方返回的 i 肯定是一個(gè) 0 到 len-1 之間的偶數(shù):
返回的這個(gè)偶數(shù) i罢洲,在標(biāo)號(hào)為 ② 和 ③ 的地方都有用到。
標(biāo)號(hào)為 ② 的地方是檢查傳進(jìn)來(lái)的這個(gè) key 是否在數(shù)組中已經(jīng)存在了,也就是我們說(shuō)的是否 hash 沖突惹苗。
如果沒(méi)沖突殿较,繼續(xù)往下執(zhí)行。
如果沖突了桩蓉,且 value 值存在淋纲,就替換 value 值,然后返回院究。
如果沖突了洽瞬,且 value 值不存在, i 值經(jīng)過(guò) nextKeyIndex 方法后也發(fā)生了變化业汰。
下標(biāo) i 是怎么變化的呢伙窃?
假設(shè)我們來(lái)了一個(gè) key=key2 的元素,經(jīng)過(guò) hash 計(jì)算后样漆,對(duì)應(yīng)數(shù)組下標(biāo)為 2为障,但是該位置上已經(jīng)有了一個(gè) key1 ,那么就是發(fā)生了 hash 沖突:
發(fā)生沖突放祟,i+2鳍怨,也就是找到下一個(gè)偶數(shù)下標(biāo)。
代碼中是這樣的體現(xiàn)的:
當(dāng) key2 的 identityHashCode 和 key1 一樣跪妥,發(fā)生 hash 沖突之后鞋喇,是這樣存儲(chǔ)的:
那勢(shì)必會(huì)出現(xiàn) i+2 的結(jié)果比 len 還長(zhǎng)的情況:
你發(fā)現(xiàn)源碼是怎么解決這個(gè)問(wèn)題的嗎?
這個(gè) nextkeyIndex 這個(gè)方法首尾相接眉撵,它是一個(gè)圓叭丰恪:
這種情況,這個(gè)圓执桌,畫(huà)圖是怎么體現(xiàn)的呢?
怎么樣鄙皇,是不是很騷。
執(zhí)行到編號(hào)為 ③ 的地方仰挣,就很清晰了:
key 是放在 tab[i] 的位置的伴逸。
value 是放在 tab[i+1] 的位置的。
和我們畫(huà)圖的邏輯一致膘壶。
暢游源碼-GET
接下來(lái)我們看看 get 方法:
標(biāo)號(hào)為 ① 的地方错蝴,直接取到了對(duì)應(yīng)的 key。
你注意這個(gè)地方颓芭,用的是 == 來(lái)判斷對(duì)象是否相等顷锰,hashMap 用的是 equals 。
標(biāo)號(hào)為 ② 的地方亡问,是沒(méi)有對(duì)應(yīng)的 key官紫,直接返回 null肛宋。
走到標(biāo)號(hào)為 ③ 的地方,代表這個(gè) key 發(fā)生過(guò) hash 沖突束世。那么接著找下一個(gè)偶數(shù)位下標(biāo)的 key酝陈。
比如我們這里的 key2:
整個(gè)過(guò)程還是非常清晰的。學(xué)習(xí)的時(shí)候可以和 hashMap 的 get 方法進(jìn)行對(duì)比學(xué)習(xí)毁涉。
你會(huì)發(fā)現(xiàn)沉帮,思想是一個(gè)思想,但是解決方案是完全不同的解決方案贫堰。
暢游源碼-REMOVE
接著再看最后一個(gè) remove 方法:
首先穆壕,標(biāo)號(hào)為 ① 的地方,你想到了什么東西其屏?
我看到這個(gè) modCount 可太親切了喇勋。圍繞著這個(gè)玩意,我前前后后大概寫(xiě)了有 3w 多字的文章吧:
是為了拋出 ConcurrentModificationException 服務(wù)的漫玄。
這里體現(xiàn)的是 fast-fail 的思想。
關(guān)于這個(gè)異常最經(jīng)典的一個(gè)面試題就是:ArrayList 如果一邊遍歷压彭,一邊刪除睦优,會(huì)出現(xiàn)什么情況?
什么壮不?你不會(huì)汗盘?我也不回答了。
假粉絲询一,請(qǐng)你回去等通知吧隐孽。
標(biāo)號(hào)為 ② 的地方,把 i 和 i+1 的位置都置為 null健蕊。也就是把 key 和對(duì)應(yīng)的 value 都置為 null菱阵。
執(zhí)行完標(biāo)號(hào)為 ② 的地方, remove 的操作也就完成了缩功。
那么按理來(lái)說(shuō)方法就應(yīng)該結(jié)束了晴及。對(duì)嗎?
你想一想我之前的這個(gè)圖片:
如果這個(gè)時(shí)候我要移除 key=key1 的鍵值對(duì)嫡锌,當(dāng)標(biāo)號(hào)為 ② 的地方執(zhí)行完成后虑稼,是這個(gè)樣子的:
發(fā)現(xiàn)問(wèn)題了嗎?
如果這個(gè)時(shí)候我來(lái)查詢(xún) key2,而 key2 經(jīng)過(guò) hash 方法后計(jì)算出來(lái)的 i 還是 2势木,而對(duì)應(yīng)位置上的值是 null:
這個(gè)時(shí)候你告訴我 key2 查不到蛛倦,返回一個(gè) null 給我?
key2啦桌,啪溯壶,沒(méi)了!
所以,標(biāo)號(hào)為 ③ 的地方就是為了解決這個(gè)問(wèn)題的茸塞。
java.util.IdentityHashMap#closeDeletion
你看這個(gè)方法標(biāo)號(hào)為 ① 的地方躲庄,自己都說(shuō)了:
朋友,因?yàn)槲覀冞@個(gè)結(jié)構(gòu)是一個(gè)圓钾虐,這個(gè)方法比較混亂噪窘。做好心理準(zhǔn)備。
然后就是一個(gè)異常復(fù)雜的 if 判斷效扫。
這個(gè)我是看懂了倔监,但是屬于只可意會(huì)不可言傳的那種,所以就不給大家分析了菌仁。大家有興趣的自己去看看浩习。
只要你抓準(zhǔn)了它的存儲(chǔ)機(jī)制和方法功能,理解起來(lái)應(yīng)該不算很費(fèi)勁济丘。
再看標(biāo)號(hào)為 ② 的地方谱秽,理解起來(lái)就很容易了,把之前由于 hash 沖突導(dǎo)致的位置偏移的數(shù)據(jù)摹迷,一個(gè)個(gè)的往前挪:
意思就是上面圖片的意思疟赊。
先把 key1 從 i=2 的位置移走。然后把 i=4 的 key2 往前移動(dòng) 2 位峡碉。
這樣近哟,下次來(lái)查詢(xún) key2 的時(shí)候,就能得到正確的返回了鲫寄。
這里留下一個(gè)疑問(wèn)吉执,假設(shè)下面這個(gè)場(chǎng)景:
key1 和 key2 是有 hash 沖突的,但是 key3 是正常的?地来。
那么移除掉 key1 之后的圖應(yīng)該是這樣的:
代碼是怎么控制或者說(shuō)怎么知道 key2 和 key1 是有沖突的戳玫,所以移走 key1 之后,需要把 key2 往前移動(dòng)未斑。而 key3 和 key2 是沒(méi)有關(guān)系的量九,所以 key3 放著不動(dòng)。
答案其實(shí)就藏在 closeDeletion 方法的源碼里面颂碧,就看你有沒(méi)有徹底理解這個(gè)方法了荠列。?
好了,到這里關(guān)于 identityHashMap 增刪改查我們就分享完畢了载城。
老規(guī)矩肌似,源碼導(dǎo)讀,點(diǎn)到為止诉瓦。
就像傳統(tǒng)功夫川队,都是點(diǎn)到為止力细。年輕人,不講武德固额,耗子尾汁...
馬老師可真是我最近一段時(shí)間的快樂(lè)源泉啊眠蚂。
咦,偏了偏了斗躏,說(shuō)編程呢逝慧,怎么說(shuō)到馬老師那邊去了。
難道我不經(jīng)意間發(fā)現(xiàn)了:萬(wàn)物皆可馬保國(guó)定律啄糙?
identityHashCode的錯(cuò)誤使用
前面說(shuō)了笛臣,IdentityHashMap 的核心點(diǎn)在于 System.identityHashCode 方法。
說(shuō)到這個(gè) identityHashCode 我又想到了曾經(jīng)在 Dubbo 中的看到的一段源碼隧饼。
位于一致性哈希負(fù)載均衡算法中:
org.apache.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance#doSelect
上面的源碼是 2.7.8 版本沈堡。
假設(shè)有五個(gè)可用的服務(wù)提供者,這里的 invokers 集合里面裝的就是一個(gè)個(gè)服務(wù)提供者燕雁。
然后調(diào)用了 invokers 诞丽,也就是 list 的 hashCode 方法。
因?yàn)橐恢滦怨5呢?fù)載均衡的思想就是當(dāng)服務(wù)發(fā)生了上下線之后拐格,我們需要對(duì)哈希環(huán)進(jìn)行調(diào)整僧免。
如果服務(wù)沒(méi)有發(fā)生上下線,那么是不需要進(jìn)行哈希環(huán)調(diào)整的禁荒。
具體到這個(gè) list 來(lái)說(shuō)就是:
當(dāng) list 里面的元素發(fā)生了變化猬膨,那么說(shuō)明有服務(wù)上下線的情況發(fā)生角撞。
至于你裝元素的 list 是否和原來(lái)的不一樣呛伴,那我是不關(guān)心的。
所以作者在這里還寫(xiě)了一個(gè)備注:我們應(yīng)該只注意 list 里面的元素就可以了谒所。
言外之意就是我剛剛說(shuō)的:裝元素的 list 是否發(fā)生了變化热康,我是不關(guān)心的。
按照開(kāi)源框架的尿性劣领,這地方專(zhuān)門(mén)寫(xiě)了一行注釋?zhuān)f(shuō)明這個(gè)地方曾經(jīng)是有問(wèn)題的姐军。
那我們看看這個(gè)地方的提交記錄:
果然,在 2019 年 12 月 11 日尖淘,有人提交了代碼奕锌。
提交的代碼如下:
你看,原來(lái)的代碼是 System.identityHashCode 方法村生。
后來(lái)修改為調(diào)用 list 的 hashCode 方法惊暴。
單單看著一行代碼,我們就知道趁桃,之前的代碼是關(guān)注 list 這個(gè)容器了辽话,導(dǎo)致了某些 bug 的出現(xiàn)肄鸽。
具體什么原因,我們可以看看這次提交對(duì)應(yīng)的 pr:
也就是編號(hào)為 5429 的 issue:
https://github.com/apache/dubbo/issues/5429
哎呀油啤,我去典徘,這誰(shuí)啊益咬?看著眼熟按濉?這不就是 why 哥嗎础废?這不是巧了嗎汛骂,這不是?
是的评腺,這個(gè) bug 就是我發(fā)現(xiàn)并提出的對(duì)應(yīng)的 issue帘瞭。
而且這個(gè) bug 其實(shí)是非常好發(fā)現(xiàn)的,只要你把環(huán)境一搭蒿讥,代碼一跑蝶念,場(chǎng)景一模擬。是個(gè)必現(xiàn)的問(wèn)題芋绸。
而產(chǎn)生這個(gè) bug 的原因媒殉,可謂是蝴蝶效應(yīng)。在離這段源碼很遠(yuǎn)的摔敛,毫不相干的一次需求中廷蓉,不知不覺(jué)的就影響到了這段代碼。
而且連開(kāi)發(fā)者自己都不知道马昙,自己的修改會(huì)影響到一致性哈希負(fù)載均衡算法桃犬。所以,根本也就談不上什么測(cè)試用例了行楞。
如果你想更進(jìn)一步了解這個(gè) bug 的來(lái)龍去脈攒暇。可以看看這篇文章:
《夠強(qiáng)子房!一行代碼就修復(fù)了我提的Dubbo的Bug》
如果你想更進(jìn)一步的了解 Dubbo 的負(fù)載均衡策略形用,那可以看看這篇文章:
《吐血輸出:2萬(wàn)字長(zhǎng)文帶你細(xì)細(xì)盤(pán)點(diǎn)五種負(fù)載均衡策略≈ず迹》
好了田度,那么這次的文章就到這里啦。給大家分享了一個(gè)冷門(mén)的解愤、"學(xué)了沒(méi)多大卵用" 的 IdentityHashMap镇饺。
你要是不喜歡下面的荒腔走板環(huán)節(jié)的話,也請(qǐng)記得拉到文章的最后琢歇。留言兰怠、點(diǎn)贊梦鉴、在看、轉(zhuǎn)發(fā)揭保、贊賞肥橙,隨便來(lái)一個(gè)就行。你要是都安排上秸侣,我也不介意存筏。
荒腔走板
最近項(xiàng)目組接到了一個(gè)工期特別緊張的項(xiàng)目。
所以剛剛過(guò)去的周末我加了兩天的班味榛。周六晚上把流程走通之后椭坚,已經(jīng)快是 22 點(diǎn)了。
之前預(yù)約了安裝家電的師傅搏色,剛好也是周六善茎。
所以只有女朋友一個(gè)人去家那邊,邊打掃衛(wèi)生频轿,邊等著安裝師傅垂涯。
安裝師傅全部弄好之后也是 19 點(diǎn)之后了。
因?yàn)槲覐墓镜郊姨貏e的近航邢。女朋友覺(jué)得我也差不多該下班了耕赘,于是決定就在家里等我,然后一起從家里回到租住的小區(qū)膳殷。
結(jié)果一等就是 2 個(gè)多小時(shí)操骡。
我下班之后,馬上打車(chē)到小區(qū)册招。
下午沒(méi)有吃飯河质,工作也比較勞累,坐在車(chē)上乐尊,一陣疲倦的感覺(jué)襲來(lái)扔嵌。
但是在小區(qū)門(mén)口刷門(mén)禁卡的時(shí)候胁勺,我一抬頭嵌洼,門(mén)口寫(xiě)著:歡迎回家麻养。
那一刻春贸,我突然覺(jué)得好暖啊,甚至還有一絲絲的感動(dòng)类垫。
走在小區(qū)的路上,感覺(jué)一切都是這么的可愛(ài)陪捷。
因?yàn)檫@個(gè)家,真的是屬于自己的家,用自己一手一腳掙出來(lái)的錢(qián)堆出來(lái)的。
此時(shí)此刻穴豫,家里還有一個(gè)人,開(kāi)著燈逼友,在等著我回家精肃。
之前我從來(lái)沒(méi)有這樣的感覺(jué)過(guò),這是一種非常神奇的感覺(jué)帜乞。
到家之后司抱,由于家具還沒(méi)有準(zhǔn)備好,我看到女朋友在地上鋪著一個(gè)泡沫墊子黎烈,坐在上面习柠,靠在墻上,通過(guò)手機(jī)看著綜藝照棋。
她起來(lái)抱了抱我资溃,說(shuō):你終于回來(lái)啦。今天的事可真是多烈炭。
我們一起站在空蕩蕩的客廳中間溶锭。
那一刻,家的含義符隙,家的感覺(jué)趴捅,從來(lái)沒(méi)有這么具體過(guò)。
最后說(shuō)一句(求關(guān)注)
才疏學(xué)淺膏执,難免會(huì)有紕漏驻售,如果你發(fā)現(xiàn)了錯(cuò)誤的地方露久,可以在留言區(qū)提出來(lái)更米,我對(duì)其加以修改。 感謝您的閱讀毫痕,我堅(jiān)持原創(chuàng)征峦,十分歡迎并感謝您的關(guān)注迟几。
我是 why,一個(gè)被代碼耽誤的文學(xué)創(chuàng)作者栏笆,不是大佬类腮,但是喜歡分享,是一個(gè)又暖又有料的四川好男人蛉加。
歡迎關(guān)注我呀蚜枢。