Java并發(fā)系列之Synchronized

每一個(gè)剛接觸多線程并發(fā)編程的同學(xué)破托,當(dāng)被問(wèn)到肪跋,如果多個(gè)線程同時(shí)訪問(wèn)一段代碼,發(fā)生并發(fā)的時(shí)候炼团,應(yīng)該怎么處理澎嚣?

我相信閃現(xiàn)在腦海中的第一個(gè)解決方案就是用synchronized,用鎖瘟芝,讓這段代碼同一時(shí)間只能被一個(gè)線程執(zhí)行易桃。
我們也知道,synchronized關(guān)鍵字可以用在方法上锌俱,也可以用在代碼塊上晤郑,如果要使用synchronized,我們一般就會(huì)如下使用:

public synchronized void doSomething() {
    //do something here
}

或者

synchronized(LockObject) {
    //do something here
}

那么實(shí)際上贸宏,synchronized關(guān)鍵字到底是怎么加鎖的造寝?鎖又長(zhǎng)什么樣子的呢?關(guān)于鎖吭练,還有一些什么樣的概念需要我們?nèi)フJ(rèn)識(shí)诫龙,去學(xué)習(xí),去理解的呢鲫咽?

以前在學(xué)習(xí)synchronized的時(shí)候签赃,就有文章說(shuō), synchronized是一個(gè)很重的操作,開(kāi)銷很大分尸,不要輕易使用锦聊,我們接受了這樣的觀點(diǎn),但是為什么說(shuō)是重的操作呢箩绍,為什么開(kāi)銷就大呢孔庭?

到j(luò)ava 1.6之后,java的開(kāi)發(fā)人員又針對(duì)鎖機(jī)制實(shí)現(xiàn)了一些優(yōu)化材蛛,又有文章告訴我們現(xiàn)在經(jīng)過(guò)優(yōu)化后圆到,使用synchronized并沒(méi)有什么太大的問(wèn)題了,那這又是因?yàn)槭裁丛蚰乇翱裕康降资亲隽耸裁磧?yōu)化构资?

那今天我們就嘗試著從鎖機(jī)制實(shí)現(xiàn)的角度,來(lái)講述一下synchronized在java虛擬機(jī)上面的適應(yīng)場(chǎng)景是怎么樣的陨簇。

由于java在1.6之后吐绵,引入了一些優(yōu)化的方案迹淌,所以我們講述synchronized,也會(huì)基于java1.6之后的版本己单。

鎖對(duì)象

首先唉窃,我們要知道鎖其實(shí)就是一個(gè)對(duì)象,java中每一個(gè)對(duì)象都能夠作為鎖纹笼。

所以我們?cè)谑褂胹ynchronized的時(shí)候纹份,

  1. 對(duì)于同步代碼塊,就得指定鎖對(duì)象廷痘。
  2. 對(duì)于修飾方法的synchronized蔓涧,默認(rèn)的鎖對(duì)象就是當(dāng)前方法的對(duì)象。
  3. 對(duì)于修飾靜態(tài)方法的synchronized笋额,其鎖對(duì)象就是此方法所對(duì)應(yīng)的類Class對(duì)象元暴。

我們知道,所謂的對(duì)象兄猩,無(wú)非也就是內(nèi)存上的一段地址茉盏,上面存放著對(duì)應(yīng)的數(shù)據(jù),那么我們就要想枢冤,作為鎖鸠姨,它跟其它的對(duì)象有什么不一樣呢?怎么知道這個(gè)對(duì)象就是鎖呢淹真?怎么知道它跟哪個(gè)線程關(guān)聯(lián)呢讶迁?它又怎么能夠控制線程對(duì)于同步代碼塊的訪問(wèn)呢蜗侈?

Markword

可以了解到在虛擬機(jī)中允蜈,對(duì)象在內(nèi)存中的存儲(chǔ)分為三部分:

  1. 對(duì)象頭
  2. 實(shí)例數(shù)據(jù)
    3 對(duì)齊填充

其中,對(duì)象頭填充的是該對(duì)象的一些運(yùn)行時(shí)數(shù)據(jù)痢士,虛擬機(jī)一般用2到3個(gè)字寬來(lái)存儲(chǔ)對(duì)象頭值纱。

  1. 數(shù)組對(duì)象,會(huì)用3個(gè)字寬來(lái)存儲(chǔ)坯汤。
  2. 非數(shù)據(jù)對(duì)象虐唠,則用2個(gè)字寬來(lái)存儲(chǔ)。

其結(jié)構(gòu)簡(jiǎn)單如下:

長(zhǎng)度 內(nèi)容 說(shuō)明
32/64bit Markword hashCode惰聂,GC分代年齡疆偿,鎖信息
32/64bit Class Metadata Address 指向?qū)ο箢愋蛿?shù)據(jù)的指針
32/64bit Array Length 數(shù)組的長(zhǎng)度(當(dāng)對(duì)象為數(shù)組時(shí))

從上表中,我們可以看到搓幌,鎖相關(guān)的信息杆故,是存在稱之為Markword中的內(nèi)存域中。

拿以下的代碼作為例子溉愁,

synchonized(LockObject) {
    //do something here
}

在對(duì)象LockObject的對(duì)象頭中处铛,當(dāng)其被創(chuàng)建的時(shí)候,其Markword的結(jié)構(gòu)如下:

bit fields 是否偏向鎖 鎖標(biāo)志位
hash age 0 01

從上面Markword的結(jié)構(gòu)中,可以看出

所有新創(chuàng)建的對(duì)象撤蟆,都是可偏向的(鎖標(biāo)志位為01)奕塑,但都是未偏向的(是否偏向鎖標(biāo)志位為0)。

偏向鎖

當(dāng)線程執(zhí)行到臨界區(qū)(critical section)時(shí)家肯,此時(shí)會(huì)利用CAS(Compare and Swap)操作龄砰,將線程ID插入到Markword中,同時(shí)修改偏向鎖的標(biāo)志位讨衣。

這說(shuō)明此對(duì)象就要被當(dāng)做一個(gè)鎖來(lái)使用换棚,那么其Markword的內(nèi)容就要發(fā)生變化了。
其結(jié)構(gòu)其會(huì)變成如下:

bit fields 是否偏向鎖 鎖標(biāo)志位
threadId epoch age 1 01

可以看到反镇,

  1. 鎖的標(biāo)志位還是01
  2. “是否偏向鎖”這個(gè)字段變成了1
  3. hash值變成了線程ID和epoch值

也就是說(shuō)固蚤,這個(gè)鎖將自己偏向了當(dāng)前線程,心里默默地藏著線程id愿险, 在這里颇蜡,我們就引入了“偏向鎖”的概念。

在此線程之后的執(zhí)行過(guò)程中辆亏,如果再次進(jìn)入或者退出同一段同步塊代碼风秤,并不再需要去進(jìn)行加鎖或者解鎖操作,而是會(huì)做以下的步驟:

  1. Load-and-test扮叨,也就是簡(jiǎn)單判斷一下當(dāng)前線程id是否與Markword當(dāng)中的線程id是否一致.
  2. 如果一致缤弦,則說(shuō)明此線程已經(jīng)成功獲得了鎖,繼續(xù)執(zhí)行下面的代碼
  3. 如果不一致彻磁,則要檢查一下對(duì)象是否還是可偏向碍沐,即“是否偏向鎖”標(biāo)志位的值。
  4. 如果還未偏向衷蜓,則利用CAS操作來(lái)競(jìng)爭(zhēng)鎖累提,也即是第一次獲取鎖時(shí)的操作。
  5. 如果此對(duì)象已經(jīng)偏向了磁浇,并且不是偏向自己斋陪,則說(shuō)明存在了競(jìng)爭(zhēng)。此時(shí)可能就要根據(jù)另外線程的情況置吓,可能是重新偏向无虚,也有可能是做偏向撤銷,但大部分情況下就是升級(jí)成輕量級(jí)鎖了衍锚。

以下是Java開(kāi)發(fā)人員提供的一張圖:

biased-locking.png

“偏向鎖”是Java在1.6引入的一種優(yōu)化機(jī)制友题,其核心思想在于,可以讓同一個(gè)線程一直擁有同一個(gè)鎖戴质,直到出現(xiàn)競(jìng)爭(zhēng)度宦,才去釋放鎖踢匣。

因?yàn)榻?jīng)過(guò)虛擬機(jī)開(kāi)發(fā)人員的調(diào)查研究,在大多數(shù)情況下斗埂,總是同一個(gè)線程去訪問(wèn)同步塊代碼符糊,基于這樣一個(gè)假設(shè),引入了偏向鎖呛凶,只需要用一個(gè)CAS操作和簡(jiǎn)單地判斷比較男娄,就可以讓一個(gè)線程持續(xù)地?fù)碛幸粋€(gè)鎖。

也正因?yàn)榇思僭O(shè)漾稀,在Jdk1.6中模闲,偏向鎖的開(kāi)關(guān)是默認(rèn)開(kāi)啟的,適用于只有一個(gè)線程訪問(wèn)同步塊的場(chǎng)景崭捍。

鎖膨脹

在上面尸折,我們講到,一旦出現(xiàn)競(jìng)爭(zhēng)殷蛇,也即有另外一個(gè)線程也要來(lái)訪問(wèn)這一段代碼实夹,偏向鎖就不適用于這種場(chǎng)景了。

如果兩個(gè)線程都是活躍的粒梦,會(huì)發(fā)生競(jìng)爭(zhēng)亮航,此時(shí)偏向鎖就會(huì)發(fā)生升級(jí),也就是我們常常聽(tīng)到的鎖膨脹匀们。

偏向鎖會(huì)膨脹成輕量級(jí)鎖(lightweight locking)缴淋。

鎖撤銷

偏向鎖有一個(gè)不好的點(diǎn)就是,一旦出現(xiàn)多線程競(jìng)爭(zhēng)泄朴,需要升級(jí)成輕量級(jí)鎖重抖,是有可能需要先做出銷撤銷的操作。

而銷撤銷的操作祖灰,相對(duì)來(lái)說(shuō)钟沛,開(kāi)銷就會(huì)比較大,其步驟如下:

  1. 在一個(gè)安全點(diǎn)停止擁有鎖的線程局扶,就跟開(kāi)始做GC操作一樣恨统。
  2. 遍歷線程棧,如果存在鎖記錄的話详民,需要修復(fù)鎖記錄和Markword延欠,使其變成無(wú)鎖狀態(tài)陌兑。
  3. 喚醒當(dāng)前線程沈跨,將當(dāng)前鎖升級(jí)成輕量級(jí)鎖。

輕量級(jí)鎖

而本質(zhì)上呢兔综,其實(shí)就是鎖對(duì)象頭中的Markword內(nèi)容又要發(fā)生變化了饿凛。

下面先簡(jiǎn)單地描述 其膨脹的步驟:

  1. 線程在自己的棧楨中創(chuàng)建鎖記錄 LockRecord
  2. 將鎖對(duì)象的對(duì)象頭中的MarkWord復(fù)制到線程的剛剛創(chuàng)建的鎖記錄中
  3. 將鎖記錄中的Owner指針指向鎖對(duì)象
  4. 將鎖對(duì)象的對(duì)象頭的MarkWord替換為指向鎖記錄的指針狞玛。

同樣,我們還是利用Java開(kāi)發(fā)人員提供的一張圖來(lái)描述此步驟:

lightweight-locking-01.png
lightweight-locking-02.png

可以根據(jù)上面兩圖來(lái)印證上面幾個(gè)步驟涧窒,但在這里心肪,其實(shí)對(duì)象的Markword其實(shí)也是發(fā)生了變化的,其現(xiàn)在的內(nèi)容結(jié)構(gòu)如下:

bit fields 鎖標(biāo)志位
指向LockRecord的指針 00

說(shuō)到這里纠吴,我們又通過(guò)偏向鎖引入了輕量級(jí)鎖的概念硬鞍,那么輕量級(jí)鎖是怎么個(gè)輕量級(jí)法,它具體的實(shí)現(xiàn)又是怎么樣的呢戴已?

就像偏向鎖的前提固该,是同步代碼塊在大多數(shù)情況下只有同一個(gè)線程訪問(wèn)的時(shí)候。
而輕量級(jí)鎖的前提則是糖儡,線程在同步代碼塊里面的操作非撤セ担快,獲取鎖之后握联,很快就結(jié)束操作桦沉,然后將鎖釋放出來(lái)。

但是不管再怎么快金闽,一旦一個(gè)線程獲得鎖了纯露,那么另一個(gè)線程同時(shí)也來(lái)訪問(wèn)這段代碼時(shí),怎么辦呢呐矾?這就涉及到我們下面所說(shuō)的鎖自旋的概念了苔埋。

自旋鎖/自適應(yīng)自旋鎖

來(lái)到輕量級(jí)鎖,其實(shí)輕量級(jí)的敘述就來(lái)自于自旋的概念蜒犯。
因?yàn)榍疤崾蔷€程在臨界區(qū)的操作非匙殚希快,所以它會(huì)非撤K妫快速地釋放鎖玉工,所以只要讓另外一個(gè)線程在那里地循環(huán)等待,然后當(dāng)鎖被釋放時(shí)淘菩,它馬上就能夠獲得鎖遵班,然后進(jìn)入臨界區(qū)執(zhí)行,然后馬上又釋放鎖潮改,讓給另外一個(gè)線程狭郑。
所謂自旋,就是線程在原地空循環(huán)地等待汇在,不阻塞翰萨,但它是消耗CPU的。
所以對(duì)于輕量級(jí)鎖糕殉,它也有其限制所在:

  1. 因?yàn)橄腃PU亩鬼,所以自旋的次數(shù)是有限的殖告,如果自旋到達(dá)一定的次數(shù)之后,還獲取不到鎖雳锋,那這種自旋也就無(wú)意義黄绩。但在上述的前提下,這種自旋的次數(shù)還是比較少的(經(jīng)驗(yàn)數(shù)據(jù))玷过。

    當(dāng)然爽丹,一開(kāi)始的自旋次數(shù)都是固定的,但是在經(jīng)驗(yàn)代碼中辛蚊,獲得鎖的線程通常能夠馬上再獲得鎖习劫,所以又引入了自適應(yīng)的自旋,即根據(jù)上次獲得鎖的情況和當(dāng)前的線程狀態(tài)嚼隘,動(dòng)態(tài)地修改當(dāng)前線程自旋的次數(shù)诽里。

  2. 當(dāng)另一個(gè)線程釋放鎖之后,當(dāng)前線程要能夠馬上獲得鎖飞蛹,所以如果有超過(guò)兩個(gè)的線程同時(shí)訪問(wèn)這段代碼谤狡,就算另外一個(gè)線程釋放鎖之后,當(dāng)前線程也可能獲取不到鎖卧檐,還是要繼續(xù)等待墓懂,空耗CPU。

從以上兩點(diǎn)可以看出霉囚,當(dāng)線程通過(guò)自旋獲取不到鎖了捕仔,比如臨界區(qū)的操作太花時(shí)間了,或者有超過(guò)2個(gè)以上的線程在競(jìng)爭(zhēng)鎖了盈罐,輕量級(jí)鎖的前提又不成立了榜跌。當(dāng)虛擬機(jī)檢查到這種情況時(shí),又開(kāi)始了膨脹的腳步盅粪。

互斥鎖(重量級(jí)鎖)

相比起輕量級(jí)鎖钓葫,再膨脹的鎖,一般稱之為重量級(jí)鎖票顾,因?yàn)槭且蕾囉诿總€(gè)對(duì)象內(nèi)部都有的monitor鎖來(lái)實(shí)現(xiàn)的础浮,而monitor又依賴于操作系統(tǒng)的MutexLock(互斥鎖)來(lái)實(shí)現(xiàn),所以一般重量級(jí)鎖也叫互斥鎖奠骄。

由于需要在操作系統(tǒng)的內(nèi)核態(tài)和用戶態(tài)之間切換的豆同,需要將線程阻塞掛起,切換線程的上下文含鳞,再恢復(fù)等操作影锈,所以當(dāng)synchronized升級(jí)成互斥鎖,依賴monitor的時(shí)候,開(kāi)銷就比較大了精居,而這也是之前為什么說(shuō)synchronized是一個(gè)很重的操作的原因了。

當(dāng)然潜必,升級(jí)成互斥鎖之后靴姿,鎖對(duì)象頭的Markword內(nèi)容也是會(huì)變化的,其內(nèi)容如下:

bit fields 鎖標(biāo)志位
指向Mutex的指針 10

每次檢查當(dāng)前線程是否獲得鎖磁滚,其實(shí)就是檢查Mutex的值是否為0佛吓,不為0,說(shuō)明其為其線程所占有垂攘,此時(shí)操作系統(tǒng)就會(huì)介入维雇,將線程阻塞,掛起晒他,釋放CPU時(shí)間吱型,等待下一次的線程調(diào)度。

好了陨仅,到這里津滞,對(duì)于synchronized所修改的同步方法或者同步代碼塊,虛擬機(jī)是如何操作的灼伤,大家應(yīng)該也有一個(gè)簡(jiǎn)單的印象了触徐。

當(dāng)使用synchronized關(guān)鍵字的時(shí)候,在java1.6之后狐赡,根據(jù)不同的條件和場(chǎng)景撞鹉,虛擬機(jī)是一步一步地將偏向鎖升級(jí)成輕量級(jí)鎖,再最終升級(jí)成重量級(jí)鎖的颖侄,而這個(gè)過(guò)程是不可逆的鸟雏,因?yàn)橐坏┥?jí)成重量級(jí)鎖,則說(shuō)明偏向鎖和輕量級(jí)鎖是不適用于當(dāng)前的應(yīng)用場(chǎng)景的览祖,那再降級(jí)回去也沒(méi)什么意義崔慧。

從這一點(diǎn),也可以看出穴墅,如果我們的應(yīng)用場(chǎng)景本身就不適用于偏向鎖和輕量級(jí)鎖惶室,那么我們?cè)诔绦蛞婚_(kāi)始,就應(yīng)該禁用掉偏向鎖和輕量級(jí)鎖玄货,直接使用重量級(jí)鎖皇钞,省去無(wú)謂的開(kāi)銷。

總結(jié)

在這里總結(jié)一下松捉,在使用synchronized關(guān)鍵字的時(shí)候夹界,本質(zhì)上是否獲得鎖,是通過(guò)修改鎖對(duì)象頭中的markword的內(nèi)容來(lái)標(biāo)記是否獲得鎖隘世,并由虛擬機(jī)來(lái)根據(jù)具體的應(yīng)用場(chǎng)景來(lái)鎖進(jìn)行升級(jí)可柿。

簡(jiǎn)單地將上述幾個(gè)零散的markword變化合在一起鸠踪,展示在下面:

鎖狀態(tài) bits 1bit是否是偏向鎖 2bit鎖標(biāo)志位
無(wú)鎖狀態(tài) 對(duì)象的hashCode 0 01
偏向鎖 線程ID 1 01
輕量級(jí)鎖 指向棧中鎖記錄的指針 0 00
重量級(jí)鎖 指向互斥量的指針 0 10

【參考資料】

Eliminating Synchronization Related Atomic Operations with Biased Locking and Bulk Rebiasing

深入理解java:2.2. 同步鎖Synchronized及其實(shí)現(xiàn)原理

java多線程synchronized底層實(shí)現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市复斥,隨后出現(xiàn)的幾起案子营密,更是在濱河造成了極大的恐慌,老刑警劉巖目锭,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件评汰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡痢虹,警方通過(guò)查閱死者的電腦和手機(jī)被去,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)奖唯,“玉大人惨缆,你說(shuō)我怎么就攤上這事》峤荩” “怎么了踪央?”我有些...
    開(kāi)封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)瓢阴。 經(jīng)常有香客問(wèn)我畅蹂,道長(zhǎng),這世上最難降的妖魔是什么荣恐? 我笑而不...
    開(kāi)封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任液斜,我火速辦了婚禮,結(jié)果婚禮上叠穆,老公的妹妹穿的比我還像新娘少漆。我一直安慰自己,他們只是感情好硼被,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布示损。 她就那樣靜靜地躺著,像睡著了一般嚷硫。 火紅的嫁衣襯著肌膚如雪检访。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天仔掸,我揣著相機(jī)與錄音脆贵,去河邊找鬼。 笑死起暮,一個(gè)胖子當(dāng)著我的面吹牛卖氨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼筒捺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼柏腻!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起系吭,我...
    開(kāi)封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤五嫂,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后村斟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抛猫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年蟆盹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闺金。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逾滥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出败匹,到底是詐尸還是另有隱情寨昙,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布掀亩,位于F島的核電站舔哪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏槽棍。R本人自食惡果不足惜捉蚤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望炼七。 院中可真熱鬧缆巧,春花似錦、人聲如沸豌拙。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)按傅。三九已至捉超,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唯绍,已是汗流浹背狂秦。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留推捐,地道東北人裂问。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親堪簿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痊乾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

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