深度分析:鎖升級(jí)過(guò)程和鎖狀態(tài)慧邮,看完這篇你就懂了调限!

一、前言

鎖的狀態(tài)總共有四種误澳,級(jí)別由低到高依次為:無(wú)鎖耻矮、偏向鎖、輕量級(jí)鎖忆谓、重量級(jí)鎖裆装,這四種鎖狀態(tài)分別代表什么,為什么會(huì)有鎖升級(jí)倡缠?其實(shí)在 JDK 1.6之前哨免,synchronized 還是一個(gè)重量級(jí)鎖,是一個(gè)效率比較低下的鎖昙沦,但是在JDK 1.6后琢唾,Jvm為了提高鎖的獲取與釋放效率對(duì)(synchronized )進(jìn)行了優(yōu)化,引入了 偏向鎖 和 輕量級(jí)鎖 桅滋,從此以后鎖的狀態(tài)就有了四種(無(wú)鎖慧耍、偏向鎖身辨、輕量級(jí)鎖、重量級(jí)鎖)芍碧,并且四種狀態(tài)會(huì)隨著競(jìng)爭(zhēng)的情況逐漸升級(jí)煌珊,而且是不可逆的過(guò)程,即不可降級(jí)泌豆,也就是說(shuō)只能進(jìn)行鎖升級(jí)(從低級(jí)別到高級(jí)別)定庵,不能鎖降級(jí)(高級(jí)別到低級(jí)別),意味著偏向鎖升級(jí)成輕量級(jí)鎖后不能降級(jí)成偏向鎖踪危。這種鎖升級(jí)卻不能降級(jí)的策略蔬浙,目的是為了提高獲得鎖和釋放鎖的效率。

二贞远、鎖的四種狀態(tài)

在 synchronized 最初的實(shí)現(xiàn)方式是 “阻塞或喚醒一個(gè)Java線程需要操作系統(tǒng)切換CPU狀態(tài)來(lái)完成畴博,這種狀態(tài)切換需要耗費(fèi)處理器時(shí)間,如果同步代碼塊中內(nèi)容過(guò)于簡(jiǎn)單蓝仲,這種切換的時(shí)間可能比用戶代碼執(zhí)行的時(shí)間還長(zhǎng)”俱病,這種方式就是 synchronized實(shí)現(xiàn)同步最初的方式,這也是當(dāng)初開(kāi)發(fā)者詬病的地方袱结,這也是在JDK6以前 synchronized效率低下的原因亮隙,JDK6中為了減少獲得鎖和釋放鎖帶來(lái)的性能消耗,引入了“偏向鎖”和“輕量級(jí)鎖”垢夹。

所以目前鎖狀態(tài)一種有四種溢吻,從級(jí)別由低到高依次是:無(wú)鎖、偏向鎖果元,輕量級(jí)鎖促王,重量級(jí)鎖,鎖狀態(tài)只能升級(jí)而晒,不能降級(jí)

如圖所示:


三硼砰、鎖狀態(tài)的思路以及特點(diǎn)

四、鎖對(duì)比

五欣硼、Synchronized鎖

synchronized 用的鎖是存在Java對(duì)象頭里的题翰,那么什么是對(duì)象頭呢?

5.1 Java 對(duì)象頭

我們以 Hotspot 虛擬機(jī)為例诈胜,Hopspot 對(duì)象頭主要包括兩部分?jǐn)?shù)據(jù):Mark Word(標(biāo)記字段) 和 Klass Pointer(類(lèi)型指針)

Mark Word:默認(rèn)存儲(chǔ)對(duì)象的HashCode豹障,分代年齡和鎖標(biāo)志位信息。這些信息都是與對(duì)象自身定義無(wú)關(guān)的數(shù)據(jù)焦匈,所以Mark Word被設(shè)計(jì)成一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小的空間內(nèi)存存儲(chǔ)盡量多的數(shù)據(jù)血公。它會(huì)根據(jù)對(duì)象的狀態(tài)復(fù)用自己的存儲(chǔ)空間,也就是說(shuō)在運(yùn)行期間Mark Word里存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志位的變化而變化缓熟。

Klass Point:對(duì)象指向它的類(lèi)元數(shù)據(jù)的指針累魔,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類(lèi)的實(shí)例摔笤。

在上面中我們知道了,synchronized 用的鎖是存在Java對(duì)象頭里的垦写,那么具體是存在對(duì)象頭哪里呢吕世?答案是:存在鎖對(duì)象的對(duì)象頭的Mark Word中,那么MarkWord在對(duì)象頭中到底長(zhǎng)什么樣梯投,它到底存儲(chǔ)了什么呢命辖?

在64位的虛擬機(jī)中:

在32位的虛擬機(jī)中:

下面我們以 32位虛擬機(jī)為例,來(lái)看一下其 Mark Word 的字節(jié)具體是如何分配的

無(wú)鎖 :對(duì)象頭開(kāi)辟 25bit 的空間用來(lái)存儲(chǔ)對(duì)象的 hashcode 分蓖,4bit 用于存放對(duì)象分代年齡尔艇,1bit 用來(lái)存放是否偏向鎖的標(biāo)識(shí)位,2bit 用來(lái)存放鎖標(biāo)識(shí)位為01

偏向鎖: 在偏向鎖中劃分更細(xì)么鹤,還是開(kāi)辟 25bit 的空間终娃,其中23bit 用來(lái)存放線程ID,2bit 用來(lái)存放 Epoch蒸甜,4bit 存放對(duì)象分代年齡尝抖,1bit 存放是否偏向鎖標(biāo)識(shí), 0表示無(wú)鎖迅皇,1表示偏向鎖,鎖的標(biāo)識(shí)位還是01

輕量級(jí)鎖:在輕量級(jí)鎖中直接開(kāi)辟 30bit 的空間存放指向棧中鎖記錄的指針衙熔,2bit 存放鎖的標(biāo)志位登颓,其標(biāo)志位為00

重量級(jí)鎖: 在重量級(jí)鎖中和輕量級(jí)鎖一樣,30bit 的空間用來(lái)存放指向重量級(jí)鎖的指針红氯,2bit 存放鎖的標(biāo)識(shí)位框咙,為11

GC標(biāo)記: 開(kāi)辟30bit 的內(nèi)存空間卻沒(méi)有占用,2bit 空間存放鎖標(biāo)志位為11痢甘。

其中無(wú)鎖和偏向鎖的鎖標(biāo)志位都是01喇嘱,只是在前面的1bit區(qū)分了這是無(wú)鎖狀態(tài)還是偏向鎖狀態(tài)

關(guān)于內(nèi)存的分配,我們可以在git中openJDK中 markOop.hpp 可以看出:

public:
  // Constants
  enum { age_bits                 = 4,
         lock_bits                = 2,
         biased_lock_bits         = 1,
         max_hash_bits            = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
         hash_bits                = max_hash_bits > 31 ? 31 : max_hash_bits,
         cms_bits                 = LP64_ONLY(1) NOT_LP64(0),
         epoch_bits               = 2
  };

age_bits: 就是我們說(shuō)的分代回收的標(biāo)識(shí)塞栅,占用4字節(jié)
lock_bits: 是鎖的標(biāo)志位者铜,占用2個(gè)字節(jié)
biased_lock_bits: 是是否偏向鎖的標(biāo)識(shí),占用1個(gè)字節(jié)
max_hash_bits: 是針對(duì)無(wú)鎖計(jì)算的hashcode 占用字節(jié)數(shù)量放椰,如果是32位虛擬機(jī)作烟,就是 32 - 4 - 2 -1 = 25 byte,如果是64 位虛擬機(jī)砾医,64 - 4 - 2 - 1 = 57 byte拿撩,但是會(huì)有 25 字節(jié)未使用,所以64位的 hashcode 占用 31 byte
hash_bits: 是針對(duì) 64 位虛擬機(jī)來(lái)說(shuō)如蚜,如果最大字節(jié)數(shù)大于 31压恒,則取31影暴,否則取真實(shí)的字節(jié)數(shù)
cms_bits: 不是64位虛擬機(jī)就占用 0 byte,是64位就占用 1byte
epoch_bits: 就是 epoch 所占用的字節(jié)大小探赫,2字節(jié)型宙。

5.2 Monitor

Monitor 可以理解為一個(gè)同步工具或一種同步機(jī)制,通常被描述為一個(gè)對(duì)象期吓。每一個(gè) Java 對(duì)象就有一把看不見(jiàn)的鎖早歇,稱為內(nèi)部鎖或者 Monitor 鎖。

Monitor 是線程私有的數(shù)據(jù)結(jié)構(gòu)讨勤,每一個(gè)線程都有一個(gè)可用 monitor record 列表箭跳,同時(shí)還有一個(gè)全局的可用列表。每一個(gè)被鎖住的對(duì)象都會(huì)和一個(gè) monitor 關(guān)聯(lián)潭千,同時(shí) monitor 中有一個(gè) Owner 字段存放擁有該鎖的線程的唯一標(biāo)識(shí)谱姓,表示該鎖被這個(gè)線程占用。

Synchronized是通過(guò)對(duì)象內(nèi)部的一個(gè)叫做監(jiān)視器鎖(monitor)來(lái)實(shí)現(xiàn)的刨晴,監(jiān)視器鎖本質(zhì)又是依賴于底層的操作系統(tǒng)的 Mutex Lock(互斥鎖)來(lái)實(shí)現(xiàn)的屉来。而操作系統(tǒng)實(shí)現(xiàn)線程之間的切換需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個(gè)成本非常高狈癞,狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長(zhǎng)的時(shí)間茄靠,這就是為什么 Synchronized 效率低的原因。因此蝶桶,這種依賴于操作系統(tǒng) Mutex Lock 所實(shí)現(xiàn)的鎖我們稱之為重量級(jí)鎖慨绳。

隨著鎖的競(jìng)爭(zhēng),鎖可以從偏向鎖升級(jí)到輕量級(jí)鎖真竖,再升級(jí)的重量級(jí)鎖(但是鎖的升級(jí)是單向的脐雪,也就是說(shuō)只能從低到高升級(jí),不會(huì)出現(xiàn)鎖的降級(jí))恢共。JDK 1.6中默認(rèn)是開(kāi)啟偏向鎖和輕量級(jí)鎖的战秋,我們也可以通過(guò)-XX:-UseBiasedLocking=false來(lái)禁用偏向鎖。

六讨韭、鎖的分類(lèi)

6.2 無(wú)鎖

無(wú)鎖是指沒(méi)有對(duì)資源進(jìn)行鎖定脂信,所有的線程都能訪問(wèn)并修改同一個(gè)資源,但同時(shí)只有一個(gè)線程能修改成功透硝。

無(wú)鎖的特點(diǎn)是修改操作會(huì)在循環(huán)內(nèi)進(jìn)行吉嚣,線程會(huì)不斷的嘗試修改共享資源。如果沒(méi)有沖突就修改成功并退出蹬铺,否則就會(huì)繼續(xù)循環(huán)嘗試尝哆。如果有多個(gè)線程修改同一個(gè)值,必定會(huì)有一個(gè)線程能修改成功甜攀,而其他修改失敗的線程會(huì)不斷重試直到修改成功秋泄。

6.3 偏向鎖

初次執(zhí)行到synchronized代碼塊的時(shí)候琐馆,鎖對(duì)象變成偏向鎖(通過(guò)CAS修改對(duì)象頭里的鎖標(biāo)志位),字面意思是“偏向于第一個(gè)獲得它的線程”的鎖恒序。執(zhí)行完同步代碼塊后瘦麸,線程并不會(huì)主動(dòng)釋放偏向鎖。當(dāng)?shù)诙蔚竭_(dá)同步代碼塊時(shí)歧胁,線程會(huì)判斷此時(shí)持有鎖的線程是否就是自己(持有鎖的線程ID也在對(duì)象頭里)滋饲,如果是則正常往下執(zhí)行。由于之前沒(méi)有釋放鎖喊巍,這里也就不需要重新加鎖屠缭。如果自始至終使用鎖的線程只有一個(gè),很明顯偏向鎖幾乎沒(méi)有額外開(kāi)銷(xiāo)崭参,性能極高呵曹。

偏向鎖是指當(dāng)一段同步代碼一直被同一個(gè)線程所訪問(wèn)時(shí),即不存在多個(gè)線程的競(jìng)爭(zhēng)時(shí)何暮,那么該線程在后續(xù)訪問(wèn)時(shí)便會(huì)自動(dòng)獲得鎖奄喂,從而降低獲取鎖帶來(lái)的消耗,即提高性能海洼。

當(dāng)一個(gè)線程訪問(wèn)同步代碼塊并獲取鎖時(shí)跨新,會(huì)在 Mark Word 里存儲(chǔ)鎖偏向的線程 ID。在線程進(jìn)入和退出同步塊時(shí)不再通過(guò) CAS 操作來(lái)加鎖和解鎖坏逢,而是檢測(cè) Mark Word 里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖域帐。輕量級(jí)鎖的獲取及釋放依賴多次 CAS 原子指令,而偏向鎖只需要在置換 ThreadID 的時(shí)候依賴一次 CAS 原子指令即可词疼。

偏向鎖只有遇到其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí),持有偏向鎖的線程才會(huì)釋放鎖帘腹,線程是不會(huì)主動(dòng)釋放偏向鎖的贰盗。

關(guān)于偏向鎖的撤銷(xiāo),需要等待全局安全點(diǎn)阳欲,即在某個(gè)時(shí)間點(diǎn)上沒(méi)有字節(jié)碼正在執(zhí)行時(shí)舵盈,它會(huì)先暫停擁有偏向鎖的線程,然后判斷鎖對(duì)象是否處于被鎖定狀態(tài)球化。如果線程不處于活動(dòng)狀態(tài)秽晚,則將對(duì)象頭設(shè)置成無(wú)鎖狀態(tài),并撤銷(xiāo)偏向鎖筒愚,恢復(fù)到無(wú)鎖(標(biāo)志位為01)或輕量級(jí)鎖(標(biāo)志位為00)的狀態(tài)赴蝇。

6.4 輕量級(jí)鎖(自旋鎖)

輕量級(jí)鎖是指當(dāng)鎖是偏向鎖的時(shí)候,卻被另外的線程所訪問(wèn)巢掺,此時(shí)偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖句伶,其他線程會(huì)通過(guò)自旋(關(guān)于自旋的介紹見(jiàn)文末)的形式嘗試獲取鎖劲蜻,線程不會(huì)阻塞,從而提高性能考余。

輕量級(jí)鎖的獲取主要由兩種情況:
① 當(dāng)關(guān)閉偏向鎖功能時(shí)先嬉;
② 由于多個(gè)線程競(jìng)爭(zhēng)偏向鎖導(dǎo)致偏向鎖升級(jí)為輕量級(jí)鎖。

一旦有第二個(gè)線程加入鎖競(jìng)爭(zhēng)楚堤,偏向鎖就升級(jí)為輕量級(jí)鎖(自旋鎖)疫蔓。這里要明確一下什么是鎖競(jìng)爭(zhēng):如果多個(gè)線程輪流獲取一個(gè)鎖,但是每次獲取鎖的時(shí)候都很順利身冬,沒(méi)有發(fā)生阻塞衅胀,那么就不存在鎖競(jìng)爭(zhēng)。只有當(dāng)某線程嘗試獲取鎖的時(shí)候吏恭,發(fā)現(xiàn)該鎖已經(jīng)被占用拗小,只能等待其釋放,這才發(fā)生了鎖競(jìng)爭(zhēng)樱哼。

在輕量級(jí)鎖狀態(tài)下繼續(xù)鎖競(jìng)爭(zhēng)哀九,沒(méi)有搶到鎖的線程將自旋,即不停地循環(huán)判斷鎖是否能夠被成功獲取搅幅。獲取鎖的操作阅束,其實(shí)就是通過(guò)CAS修改對(duì)象頭里的鎖標(biāo)志位。先比較當(dāng)前鎖標(biāo)志位是否為“釋放”茄唐,如果是則將其設(shè)置為“鎖定”息裸,比較并設(shè)置是原子性發(fā)生的。這就算搶到鎖了沪编,然后線程將當(dāng)前鎖的持有者信息修改為自己呼盆。

長(zhǎng)時(shí)間的自旋操作是非常消耗資源的,一個(gè)線程持有鎖蚁廓,其他線程就只能在原地空耗CPU访圃,執(zhí)行不了任何有效的任務(wù),這種現(xiàn)象叫做忙等(busy-waiting)相嵌。如果多個(gè)線程用一個(gè)鎖腿时,但是沒(méi)有發(fā)生鎖競(jìng)爭(zhēng),或者發(fā)生了很輕微的鎖競(jìng)爭(zhēng)饭宾,那么synchronized就用輕量級(jí)鎖批糟,允許短時(shí)間的忙等現(xiàn)象。這是一種折衷的想法看铆,短時(shí)間的忙等徽鼎,換取線程在用戶態(tài)和內(nèi)核態(tài)之間切換的開(kāi)銷(xiāo)。

6.4 重量級(jí)鎖

重量級(jí)鎖顯然,此忙等是有限度的(有個(gè)計(jì)數(shù)器記錄自旋次數(shù)纬傲,默認(rèn)允許循環(huán)10次满败,可以通過(guò)虛擬機(jī)參數(shù)更改)。如果鎖競(jìng)爭(zhēng)情況嚴(yán)重叹括,某個(gè)達(dá)到最大自旋次數(shù)的線程算墨,會(huì)將輕量級(jí)鎖升級(jí)為重量級(jí)鎖(依然是CAS修改鎖標(biāo)志位,但不修改持有鎖的線程ID)汁雷。當(dāng)后續(xù)線程嘗試獲取鎖時(shí)净嘀,發(fā)現(xiàn)被占用的鎖是重量級(jí)鎖,則直接將自己掛起(而不是忙等)侠讯,等待將來(lái)被喚醒挖藏。

重量級(jí)鎖是指當(dāng)有一個(gè)線程獲取鎖之后,其余所有等待獲取該鎖的線程都會(huì)處于阻塞狀態(tài)厢漩。

簡(jiǎn)言之膜眠,就是所有的控制權(quán)都交給了操作系統(tǒng),由操作系統(tǒng)來(lái)負(fù)責(zé)線程間的調(diào)度和線程的狀態(tài)變更溜嗜。而這樣會(huì)出現(xiàn)頻繁地對(duì)線程運(yùn)行狀態(tài)的切換宵膨,線程的掛起和喚醒,從而消耗大量的系統(tǒng)資

五炸宵、總結(jié)

文中講述了鎖的四種狀態(tài)以及鎖是如何一步一步升級(jí)的過(guò)程辟躏,文中有理解不到位或者有問(wèn)題的地方,歡迎大家在評(píng)論區(qū)中下方指出和交流土全,謝謝大家

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捎琐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子裹匙,更是在濱河造成了極大的恐慌瑞凑,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件概页,死亡現(xiàn)場(chǎng)離奇詭異籽御,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)绰沥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)篱蝇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)贺待,“玉大人徽曲,你說(shuō)我怎么就攤上這事◆锶” “怎么了秃臣?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我奥此,道長(zhǎng)弧哎,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任稚虎,我火速辦了婚禮撤嫩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蠢终。我一直安慰自己序攘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布寻拂。 她就那樣靜靜地躺著程奠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪祭钉。 梳的紋絲不亂的頭發(fā)上瞄沙,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音慌核,去河邊找鬼距境。 笑死,一個(gè)胖子當(dāng)著我的面吹牛遂铡,可吹牛的內(nèi)容都是我干的肮疗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼扒接,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼伪货!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起钾怔,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤碱呼,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后宗侦,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體愚臀,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年矾利,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了姑裂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡男旗,死狀恐怖舶斧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情察皇,我是刑警寧澤茴厉,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布泽台,位于F島的核電站,受9級(jí)特大地震影響矾缓,放射性物質(zhì)發(fā)生泄漏怀酷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一嗜闻、第九天 我趴在偏房一處隱蔽的房頂上張望蜕依。 院中可真熱鬧,春花似錦琉雳、人聲如沸笔横。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)吹缔。三九已至,卻和暖如春锯茄,著一層夾襖步出監(jiān)牢的瞬間厢塘,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工肌幽, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晚碾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓喂急,卻偏偏與公主長(zhǎng)得像格嘁,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子廊移,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355