第十七章毕莱、synchronized底層實(shí)現(xiàn)

synchronized代碼塊時(shí)由一對(duì)兒monitorenter/monitorexit指令實(shí)現(xiàn)的,Monitor對(duì)象時(shí)同步的基本實(shí)現(xiàn)單元颅夺;

在java6之前朋截,Monitor的實(shí)現(xiàn)完全時(shí)依靠操作系統(tǒng)內(nèi)部的互斥鎖,因?yàn)樾枰M(jìn)行用戶態(tài)到內(nèi)核態(tài)的切換吧黄,所以同步操作是一個(gè)無(wú)差別的重量級(jí)操作部服;

現(xiàn)代的jdk中,JVM對(duì)此進(jìn)行了改進(jìn)稚字,提供了三種不同的Monitor實(shí)現(xiàn)饲宿,也就是常說(shuō)的三種不同的鎖:偏斜鎖、輕量級(jí)鎖和重量級(jí)鎖胆描,大大改進(jìn)了其性能瘫想。



所謂鎖的升級(jí)、降級(jí)昌讲,就是JVM優(yōu)化synchronized運(yùn)行的機(jī)制国夜,當(dāng)JVM檢測(cè)到不同的競(jìng)爭(zhēng)狀況時(shí),會(huì)自動(dòng)切換到適合的鎖實(shí)現(xiàn)短绸,這種切換就是鎖的升級(jí)车吹、降級(jí)筹裕。

當(dāng)沒(méi)有競(jìng)爭(zhēng)出現(xiàn)時(shí),默認(rèn)會(huì)使用偏斜鎖窄驹。JVM會(huì)利用CAS操作朝卒,在對(duì)象頭上的MARK Word部分設(shè)置線程ID,以表示這個(gè)對(duì)象偏向于當(dāng)前的線程乐埠,所以并不涉及真正的互斥鎖抗斤。這樣做的假設(shè)是基于在很多應(yīng)用場(chǎng)景中,大部分對(duì)象生命周期中最多會(huì)被一個(gè)線程鎖定丈咐,使用偏斜鎖可以降低無(wú)競(jìng)爭(zhēng)開(kāi)銷瑞眼。

如果有另外的線程試圖鎖定某個(gè)已經(jīng)被偏斜過(guò)的對(duì)象,JVM就需要撤銷偏斜鎖棵逊,并切換到輕量級(jí)鎖實(shí)現(xiàn)伤疙。輕量級(jí)鎖依賴CAS操作Mark Word來(lái)試圖獲取鎖,如果重試成功辆影,就使用普通的輕量級(jí)鎖徒像;否則,進(jìn)一步升級(jí)為重量級(jí)鎖蛙讥。

當(dāng)JVM進(jìn)入安全點(diǎn)的時(shí)候厨姚,會(huì)檢查是否有閑置的Monitor,然后試圖進(jìn)行降級(jí)键菱。


目前在Java中存在兩種鎖機(jī)制:synchronized和Lock谬墙,Lock接口及其實(shí)現(xiàn)類是JDK5增加的內(nèi)容,其作者是大名鼎鼎的并發(fā)專家Doug Lea经备。

數(shù)據(jù)同步需要依賴鎖拭抬,那鎖的同步又依賴誰(shuí)?synchronized給出的答案是在軟件層面依賴JVM侵蒙,而Lock給出的方案是在硬件層面依賴特殊的CPU指令造虎,大家可能會(huì)進(jìn)一步追問(wèn):JVM底層又是如何實(shí)現(xiàn)synchronized的?



synchronized的實(shí)現(xiàn):

synrhronized關(guān)鍵字簡(jiǎn)潔纷闺、清晰算凿、語(yǔ)義明確,因此即使有了Lock接口犁功,使用的還是非常廣泛氓轰。其應(yīng)用層的語(yǔ)義是可以把任何一個(gè)非null對(duì)象 作為"鎖":

當(dāng)synchronized作用在方法上時(shí),鎖住的便是對(duì)象實(shí)例(this)浸卦;

當(dāng)作用在靜態(tài)方法時(shí)鎖住的便是對(duì)象對(duì)應(yīng)的Class實(shí)例署鸡,因?yàn)?Class數(shù)據(jù)存在于永久帶,因此靜態(tài)方法鎖相當(dāng)于該類的一個(gè)全局鎖;

當(dāng)synchronized作用于某一個(gè)對(duì)象實(shí)例時(shí)靴庆,鎖住的便是對(duì)應(yīng)的代碼塊时捌。

在 HotSpot JVM實(shí)現(xiàn)中,鎖有個(gè)專門的名字:對(duì)象監(jiān)視器炉抒。?

1.?線程狀態(tài)及狀態(tài)轉(zhuǎn)換

當(dāng)多個(gè)線程同時(shí)請(qǐng)求某個(gè)對(duì)象監(jiān)視器時(shí)奢讨,對(duì)象監(jiān)視器會(huì)設(shè)置幾種狀態(tài)用來(lái)區(qū)分請(qǐng)求的線程:

Contention List:所有請(qǐng)求鎖的線程將被首先放置到該競(jìng)爭(zhēng)隊(duì)列

Entry List:Contention List中那些有資格成為候選人的線程被移到Entry List

Wait Set:那些調(diào)用wait方法被阻塞的線程被放置到Wait Set

OnDeck:任何時(shí)刻最多只能有一個(gè)線程正在競(jìng)爭(zhēng)鎖,該線程稱為OnDeck

Owner:獲得鎖的線程稱為Owner

!Owner:釋放鎖的線程


狀態(tài)轉(zhuǎn)換

新請(qǐng)求鎖的線程將首先被加入到ConetentionList中焰薄,當(dāng)某個(gè)擁有鎖的線程(Owner狀態(tài))調(diào)用unlock之后禽笑,如果發(fā)現(xiàn) EntryList為空則從ContentionList中移動(dòng)線程到EntryList,下面說(shuō)明下ContentionList和EntryList 的實(shí)現(xiàn)方式:

1.1 ContentionList?虛擬隊(duì)列

ContentionList并不是一個(gè)真正的Queue蛤奥,而只是一個(gè)虛擬隊(duì)列,原因在于ContentionList是由Node及其next指 針邏輯構(gòu)成僚稿,并不存在一個(gè)Queue的數(shù)據(jù)結(jié)構(gòu)凡桥。ContentionList是一個(gè)后進(jìn)先出(LIFO)的隊(duì)列,每次新加入Node時(shí)都會(huì)在隊(duì)頭進(jìn)行蚀同, 通過(guò)CAS改變第一個(gè)節(jié)點(diǎn)的的指針為新增節(jié)點(diǎn)缅刽,同時(shí)設(shè)置新增節(jié)點(diǎn)的next指向后續(xù)節(jié)點(diǎn),而取得操作則發(fā)生在隊(duì)尾蠢络。顯然衰猛,該結(jié)構(gòu)其實(shí)是個(gè)Lock- Free的隊(duì)列。

因?yàn)橹挥蠴wner線程才能從隊(duì)尾取元素刹孔,也即線程出列操作無(wú)爭(zhēng)用啡省,當(dāng)然也就避免了CAS的ABA問(wèn)題。

1.2 EntryList

EntryList與ContentionList邏輯上同屬等待隊(duì)列髓霞,ContentionList會(huì)被線程并發(fā)訪問(wèn)卦睹,為了降低對(duì) ContentionList隊(duì)尾的爭(zhēng)用,而建立EntryList方库。Owner線程在unlock時(shí)會(huì)從ContentionList中遷移線程到 EntryList结序,并會(huì)指定EntryList中的某個(gè)線程(一般為Head)為Ready(OnDeck)線程。Owner線程并不是把鎖傳遞給 OnDeck線程纵潦,只是把競(jìng)爭(zhēng)鎖的權(quán)利交給OnDeck徐鹤,OnDeck線程需要重新競(jìng)爭(zhēng)鎖。這樣做雖然犧牲了一定的公平性邀层,但極大的提高了整體吞吐量返敬,在 Hotspot中把OnDeck的選擇行為稱之為“競(jìng)爭(zhēng)切換”。

OnDeck線程獲得鎖后即變?yōu)閛wner線程寥院,無(wú)法獲得鎖則會(huì)依然留在EntryList中救赐,考慮到公平性,在EntryList中的位置不 發(fā)生變化(依然在隊(duì)頭)。如果Owner線程被wait方法阻塞经磅,則轉(zhuǎn)移到WaitSet隊(duì)列泌绣;如果在某個(gè)時(shí)刻被notify/notifyAll喚醒, 則再次轉(zhuǎn)移到EntryList预厌。


2.?自旋鎖

那些處于ContetionList阿迈、EntryList、WaitSet中的線程均處于阻塞狀態(tài)轧叽,阻塞操作由操作系統(tǒng)完成(在Linxu下通 過(guò)pthread_mutex_lock函數(shù))苗沧。線程被阻塞后便進(jìn)入內(nèi)核(Linux)調(diào)度狀態(tài),這個(gè)會(huì)導(dǎo)致系統(tǒng)在用戶態(tài)與內(nèi)核態(tài)之間來(lái)回切換炭晒,嚴(yán)重影響 鎖的性能

緩解上述問(wèn)題的辦法便是自旋待逞,其原理是:當(dāng)發(fā)生爭(zhēng)用時(shí),若Owner線程能在很短的時(shí)間內(nèi)釋放鎖网严,則那些正在爭(zhēng)用線程可以稍微等一等(自旋)识樱, 在Owner線程釋放鎖后,爭(zhēng)用線程可能會(huì)立即得到鎖震束,從而避免了系統(tǒng)阻塞怜庸。但Owner運(yùn)行的時(shí)間可能會(huì)超出了臨界值,爭(zhēng)用線程自旋一段時(shí)間后還是無(wú)法 獲得鎖垢村,這時(shí)爭(zhēng)用線程則會(huì)停止自旋進(jìn)入阻塞狀態(tài)(后退)割疾。基本思路就是自旋嘉栓,不成功再阻塞宏榕,盡量降低阻塞的可能性,這對(duì)那些執(zhí)行時(shí)間很短的代碼塊來(lái)說(shuō)有非 常重要的性能提高侵佃。自旋鎖有個(gè)更貼切的名字:自旋-指數(shù)后退鎖担扑,也即復(fù)合鎖。很顯然趣钱,自旋在多處理器上才有意義涌献。

還有個(gè)問(wèn)題是,線程自旋時(shí)做些啥首有?其實(shí)啥都不做燕垃,可以執(zhí)行幾次for循環(huán),可以執(zhí)行幾條空的匯編指令井联,目的是占著CPU不放卜壕,等待獲取鎖的機(jī) 會(huì)。所以說(shuō)烙常,自旋是把雙刃劍轴捎,如果旋的時(shí)間過(guò)長(zhǎng)會(huì)影響整體性能鹤盒,時(shí)間過(guò)短又達(dá)不到延遲阻塞的目的。顯然侦副,自旋的周期選擇顯得非常重要侦锯,但這與操作系統(tǒng)、硬 件體系秦驯、系統(tǒng)的負(fù)載等諸多場(chǎng)景相關(guān)尺碰,很難選擇,如果選擇不當(dāng)译隘,不但性能得不到提高亲桥,可能還會(huì)下降,因此大家普遍認(rèn)為自旋鎖不具有擴(kuò)展性固耘。

自旋優(yōu)化策略

對(duì)自旋鎖周期的選擇上题篷,HotSpot認(rèn)為最佳時(shí)間應(yīng)是一個(gè)線程上下文切換的時(shí)間,但目前并沒(méi)有做到厅目。經(jīng)過(guò)調(diào)查番枚,目前只是通過(guò)匯編暫停了幾個(gè)CPU周期,除了自旋周期選擇璧瞬,HotSpot還進(jìn)行許多其他的自旋優(yōu)化策略,具體如下:

如果平均負(fù)載小于CPUs則一直自旋

如果有超過(guò)(CPUs/2)個(gè)線程正在自旋渐夸,則后來(lái)線程直接阻塞

如果正在自旋的線程發(fā)現(xiàn)Owner發(fā)生了變化則延遲自旋時(shí)間(自旋計(jì)數(shù))或進(jìn)入阻塞

如果CPU處于節(jié)電模式則停止自旋

自旋時(shí)間的最壞情況是CPU的存儲(chǔ)延遲(CPU A存儲(chǔ)了一個(gè)數(shù)據(jù)嗤锉,到CPU B得知這個(gè)數(shù)據(jù)直接的時(shí)間差)

自旋時(shí)會(huì)適當(dāng)放棄線程優(yōu)先級(jí)之間的差異

那synchronized實(shí)現(xiàn)何時(shí)使用了自旋鎖?答案是在線程進(jìn)入ContentionList時(shí)墓塌,也即第一步操作前瘟忱。線程在進(jìn)入等待隊(duì)列時(shí) 首先進(jìn)行自旋嘗試獲得鎖,如果不成功再進(jìn)入等待隊(duì)列苫幢。這對(duì)那些已經(jīng)在等待隊(duì)列中的線程來(lái)說(shuō)访诱,稍微顯得不公平。還有一個(gè)不公平的地方是自旋線程可能會(huì)搶占了 Ready線程的鎖韩肝。自旋鎖由每個(gè)監(jiān)視對(duì)象維護(hù)触菜,每個(gè)監(jiān)視對(duì)象一個(gè)。


3. JVM1.6偏向鎖

在JVM1.6中引入了偏向鎖哀峻,偏向鎖主要解決無(wú)競(jìng)爭(zhēng)下的鎖性能問(wèn)題涡相,首先我們看下無(wú)競(jìng)爭(zhēng)下鎖存在什么問(wèn)題:

現(xiàn)在幾乎所有的鎖都是可重入的,也即已經(jīng)獲得鎖的線程可以多次鎖住/解鎖監(jiān)視對(duì)象剩蟀,按照之前的HotSpot設(shè)計(jì)催蝗,每次加鎖/解鎖都會(huì)涉及到一些CAS操 作(比如對(duì)等待隊(duì)列的CAS操作),CAS操作會(huì)延遲本地調(diào)用育特,因此偏向鎖的想法是一旦線程第一次獲得了監(jiān)視對(duì)象丙号,之后讓監(jiān)視對(duì)象“偏向”這個(gè) 線程,之后的多次調(diào)用則可以避免CAS操作,說(shuō)白了就是置個(gè)變量犬缨,如果發(fā)現(xiàn)為true則無(wú)需再走各種加鎖/解鎖流程喳魏。但還有很多概念需要解釋、很多引入的 問(wèn)題需要解決:

3.1 CAS及SMP架構(gòu)

CAS為什么會(huì)引入本地延遲遍尺?這要從SMP(對(duì)稱多處理器)架構(gòu)說(shuō)起截酷,下圖大概表明了SMP的結(jié)構(gòu):

其意思是所有的CPU會(huì)共享一條系統(tǒng)總線(BUS),靠此總線連接主存乾戏。每個(gè)核都有自己的一級(jí)緩存迂苛,各核相對(duì)于BUS對(duì)稱分布,因此這種結(jié)構(gòu)稱為“對(duì)稱多處理器”鼓择。

而CAS的全稱為Compare-And-Swap三幻,是一條CPU的原子指令,其作用是讓CPU比較后原子地更新某個(gè)位置的值呐能,經(jīng)過(guò)調(diào)查發(fā)現(xiàn)念搬, 其實(shí)現(xiàn)方式是基于硬件平臺(tái)的匯編指令,就是說(shuō)CAS是靠硬件實(shí)現(xiàn)的摆出,JVM只是封裝了匯編調(diào)用朗徊,那些AtomicInteger類便是使用了這些封裝后的 接口。

Core1和Core2可能會(huì)同時(shí)把主存中某個(gè)位置的值Load到自己的L1 Cache中偎漫,當(dāng)Core1在自己的L1 Cache中修改這個(gè)位置的值時(shí)爷恳,會(huì)通過(guò)總線,使Core2中L1 Cache對(duì)應(yīng)的值“失效”象踊,而Core2一旦發(fā)現(xiàn)自己L1 Cache中的值失效(稱為Cache命中缺失)則會(huì)通過(guò)總線從內(nèi)存中加載該地址最新的值温亲,大家通過(guò)總線的來(lái)回通信稱為“Cache一致性流量”,因?yàn)榭?線被設(shè)計(jì)為固定的“通信能力”杯矩,如果Cache一致性流量過(guò)大栈虚,總線將成為瓶頸。而當(dāng)Core1和Core2中的值再次一致時(shí)史隆,稱為“Cache一致 性”魂务,從這個(gè)層面來(lái)說(shuō),鎖設(shè)計(jì)的終極目標(biāo)便是減少Cache一致性流量泌射。

而CAS恰好會(huì)導(dǎo)致Cache一致性流量头镊,如果有很多線程都共享同一個(gè)對(duì)象,當(dāng)某個(gè)Core CAS成功時(shí)必然會(huì)引起總線風(fēng)暴魄幕,這就是所謂的本地延遲相艇,本質(zhì)上偏向鎖就是為了消除CAS,降低Cache一致性流量纯陨。

Cache一致性:

上面提到Cache一致性坛芽,其實(shí)是有協(xié)議支持的留储,現(xiàn)在通用的協(xié)議是MESI(最早由Intel開(kāi)始支持),具體參考:http://en.wikipedia.org/wiki/MESI_protocol咙轩,以后會(huì)仔細(xì)講解這部分获讳。

Cache一致性流量的例外情況:

其實(shí)也不是所有的CAS都會(huì)導(dǎo)致總線風(fēng)暴,這跟Cache一致性協(xié)議有關(guān)活喊,具體參考:http://blogs.oracle.com/dave/entry/biased_locking_in_hotspot

NUMA(Non Uniform Memory Access Achitecture)架構(gòu):

與SMP對(duì)應(yīng)還有非對(duì)稱多處理器架構(gòu)丐膝,現(xiàn)在主要應(yīng)用在一些高端處理器上,主要特點(diǎn)是沒(méi)有總線钾菊,沒(méi)有公用主存帅矗,每個(gè)Core有自己的內(nèi)存,針對(duì)這種結(jié)構(gòu)此處不做討論煞烫。

3.2?偏向解除

偏向鎖引入的一個(gè)重要問(wèn)題是浑此,在多爭(zhēng)用的場(chǎng)景下,如果另外一個(gè)線程爭(zhēng)用偏向?qū)ο笾拖辏瑩碛姓咝枰尫牌蜴i凛俱,而釋放的過(guò)程會(huì)帶來(lái)一些性能開(kāi)銷,但總體說(shuō)來(lái)偏向鎖帶來(lái)的好處還是大于CAS代價(jià)的料饥。


4.?總結(jié)

關(guān)于鎖蒲犬,JVM中還引入了一些其他技術(shù)比如鎖膨脹等,這些與自旋鎖岸啡、偏向鎖相比影響不是很大原叮,這里就不做介紹。

通過(guò)上面的介紹可以看出凰狞,synchronized的底層實(shí)現(xiàn)主要依靠Lock-Free的隊(duì)列篇裁,基本思路是自旋后阻塞沛慢,競(jìng)爭(zhēng)切換后繼續(xù)競(jìng)爭(zhēng)鎖赡若,稍微犧牲了公平性,但獲得了高吞吐量团甲。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末逾冬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子躺苦,更是在濱河造成了極大的恐慌身腻,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匹厘,死亡現(xiàn)場(chǎng)離奇詭異嘀趟,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)愈诚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門她按,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)牛隅,“玉大人,你說(shuō)我怎么就攤上這事酌泰∶接叮” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵陵刹,是天一觀的道長(zhǎng)默伍。 經(jīng)常有香客問(wèn)我,道長(zhǎng)衰琐,這世上最難降的妖魔是什么也糊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮碘耳,結(jié)果婚禮上显设,老公的妹妹穿的比我還像新娘。我一直安慰自己辛辨,他們只是感情好捕捂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著斗搞,像睡著了一般指攒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上僻焚,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天允悦,我揣著相機(jī)與錄音,去河邊找鬼虑啤。 笑死隙弛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的狞山。 我是一名探鬼主播全闷,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼萍启!你這毒婦竟也來(lái)了总珠?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤勘纯,失蹤者是張志新(化名)和其女友劉穎局服,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體驳遵,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡淫奔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了堤结。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唆迁。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡佳鳖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出媒惕,到底是詐尸還是另有隱情萨西,我是刑警寧澤刽射,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響唱蒸,放射性物質(zhì)發(fā)生泄漏鸥咖。R本人自食惡果不足惜俊犯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一抱环、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧菜皂,春花似錦贞绵、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至章母,卻和暖如春母蛛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乳怎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工彩郊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚪缀。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓秫逝,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親询枚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子违帆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

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