淺談 Java 內(nèi)存模型

Java 內(nèi)存模型(JMM)描述了 JVM 如何使用計(jì)算機(jī)的內(nèi)存(RAM)。JVM 是一個(gè)完整計(jì)算機(jī)的模型惯驼,因此該模型包含了內(nèi)存模型的設(shè)計(jì) —— JMM。

如果要正確地設(shè)計(jì)并發(fā)程序,了解 JMM 非常重要汁雷。JMM 描述了不同線程間如何以及何時(shí)可以看到其它線程寫(xiě)入共享變量的值,以及如何在必要時(shí)同步訪問(wèn)共享變量骇扇。

最初的 JMM 設(shè)計(jì)不充分摔竿,因此 JMM 在 Java 1.5 進(jìn)行了修訂。此版本的 JMM 仍在 Java 8 中使用少孝。

Java Memory Model 內(nèi)部實(shí)現(xiàn)

JVM 內(nèi)部使用的 JMM 將內(nèi)存劃分為線程棧和堆继低。下圖從邏輯角度說(shuō)明了 JMM:

在 JVM 中運(yùn)行的每個(gè)線程都有它自己的線程棧,線程棧包含了線程調(diào)用了哪些方法以到達(dá)當(dāng)前執(zhí)行點(diǎn)的信息稍走,我們把它成為“調(diào)用棧(Call Stack)“袁翁。當(dāng)線程執(zhí)行其代碼時(shí),調(diào)用棧會(huì)發(fā)生變化婿脸。

線程棧還包含了正在執(zhí)行的每個(gè)方法的所有的局部變量(調(diào)用棧上的所有方法)粱胜。一個(gè)線程只能訪問(wèn)它自己的線程棧,由線程創(chuàng)建的局部變量對(duì)于創(chuàng)建它的線程以外的所有其他線程都是不可見(jiàn)的狐树。即使兩個(gè)線程正在執(zhí)行完全相同的代碼焙压,兩個(gè)線程仍將在各自的線程棧中創(chuàng)建自己的局部變量。因此抑钟,每個(gè)線程都有自己的每個(gè)局部變量的版本涯曲。

基本類(lèi)型(boolean,byte在塔,short幻件,char,int蛔溃,long绰沥,float,double)完全存儲(chǔ)在線程棧里贺待,因此對(duì)其他線程是不可見(jiàn)的徽曲。一個(gè)線程可以將一個(gè)基本類(lèi)型的變量副本傳遞給另一個(gè)線程,但它不能共享原始局部變量本身麸塞。

堆包含了 Java 應(yīng)用程序中創(chuàng)建的所有對(duì)象秃臣,不管對(duì)象是哪個(gè)線程創(chuàng)建的,這包括基本類(lèi)型的包裝版本(如 Byte喘垂,Integer甜刻,Long 等)绍撞。無(wú)論對(duì)象是創(chuàng)建成局部變量,還是作為另一個(gè)對(duì)象的成員變量被創(chuàng)建得院,對(duì)象都存儲(chǔ)在堆中傻铣。

下圖說(shuō)明了調(diào)用棧和局部變量存儲(chǔ)在線程棧中,而對(duì)象存儲(chǔ)在堆中祥绞。

局部變量如果是基本類(lèi)型非洲,這種情況下,變量完全存儲(chǔ)在線程棧上蜕径。

局部變量如果是對(duì)象的引用两踏,這種情況下,引用(局部變量)存儲(chǔ)在線程棧上兜喻,但對(duì)象本身存儲(chǔ)在堆上梦染。

對(duì)象中可能包含方法,而這些方法中可能包含局部變量朴皆,這種情況下帕识,即使方法所屬的對(duì)象存儲(chǔ)在堆上,但這些局部變量卻是存儲(chǔ)在線程棧上的遂铡。

對(duì)象的成員變量與對(duì)象本身一起存儲(chǔ)在堆上肮疗,當(dāng)成員變量是基本類(lèi)型以及是對(duì)象的引用時(shí)都是如此。

靜態(tài)類(lèi)型變量與類(lèi)定義一起存儲(chǔ)在堆上扒接。

所有線程通過(guò)擁有對(duì)象引用去訪問(wèn)堆中的對(duì)象伪货。當(dāng)一個(gè)線程有權(quán)訪問(wèn)一個(gè)對(duì)象時(shí),它也能訪問(wèn)該對(duì)象的成員變量钾怔。如果兩個(gè)線程同一時(shí)間調(diào)用同一對(duì)象的一個(gè)方法碱呼,它們都可以訪問(wèn)該對(duì)象的成員變量,但每個(gè)線程都有自己局部變量的副本蒂教。

這是一個(gè)說(shuō)明上述要點(diǎn)的圖表:

兩個(gè)線程各有一組局部變量巍举,其中一個(gè)局部變量(Local Variable 2)指向堆中的共享對(duì)象(Object 3)脆荷。兩個(gè)線程各自對(duì)同一各對(duì)象擁有不同的引用凝垛,它們的引用是局部變量,因此它們存儲(chǔ)在各自線程的線程棧中蜓谋。但是梦皮,這兩個(gè)不同引用指向堆中的同一個(gè)對(duì)象。

請(qǐng)注意桃焕,共享對(duì)象(Object 3)將 Object 2 和 Object 4 作為成員變量引用(如從 Object 3 到 Object 2 和 Object 4 的箭頭所示)剑肯,通過(guò)對(duì)象 3 中的這些成員變量引用,兩個(gè)線程可以訪問(wèn)對(duì)象 2 和 對(duì)象 4观堂。

上圖還顯示了一個(gè)局部變量指向堆中的兩個(gè)不同對(duì)象让网。這種情況下呀忧,引用指向兩個(gè)不同的對(duì)象(Object 1 和 Object 5),而不是同一個(gè)對(duì)象溃睹。理論上而账,如果兩個(gè)線程都引用了兩個(gè)對(duì)象,那兩個(gè)線程都可以訪問(wèn)對(duì)象 1 和 對(duì)象 5因篇。但在上圖中泞辐,每個(gè)線程只引用了兩個(gè)對(duì)象中的一個(gè)。

那么竞滓,什么樣的 Java 代碼可以導(dǎo)致上面的內(nèi)存圖咐吼?好吧,代碼就如下面的代碼一樣簡(jiǎn)單:

public class MyRunnable implements Runnable() {

    public void run() {
        methodOne();
    }

    public void methodOne() {
        int localVariable1 = 45;

        MySharedObject localVariable2 =
            MySharedObject.sharedInstance;

        //... 使用局部變量做更多事情.

        methodTwo();
    }

    public void methodTwo() {
        Integer localVariable1 = new Integer(99);

        //... 使用局部變量做更多事情.
    }
}
public class MySharedObject {

    // 指向MySharedObject實(shí)例的靜態(tài)變量

    public static final MySharedObject sharedInstance =
        new MySharedObject();


    // 成員變量指向堆上的兩個(gè)對(duì)象

    public Integer object2 = new Integer(22);
    public Integer object4 = new Integer(44);

    public long member1 = 12345;
    public long member1 = 67890;
}

如果兩個(gè)線程正在執(zhí)行 run() 方法商佑,則前面的結(jié)果就會(huì)出現(xiàn)锯茄。run() 方法會(huì)調(diào)用 methodOne(),而 methodOne() 會(huì)調(diào)用 methodTwo()茶没。

方法 methodOne() 中聲明了一個(gè)基本類(lèi)型的局部變量(localVariable1 類(lèi)型 int)和一個(gè)對(duì)象引用的局部變量(localVariable2)撇吞。

每個(gè)執(zhí)行 methodOne() 的線程將在各自的線程棧上創(chuàng)建自己的 localVariable1 和 localVariable2 副本。localVariable 1 變量將完全分離礁叔,只存在于每個(gè)線程的線程棧中牍颈。一個(gè)線程無(wú)法看到另一個(gè)線程對(duì)其 localVariable 1 副本所做的更改。

執(zhí)行 methodOne() 的每個(gè)線程還將創(chuàng)建它們自己的 localVariable2 副本琅关。然而煮岁,localVariable 2 的兩個(gè)不同副本最終都指向堆上的同一個(gè)對(duì)象。代碼將 localVariable 2 設(shè)置為指向靜態(tài)變量引用的對(duì)象涣易。靜態(tài)變量只有一個(gè)副本画机,這個(gè)副本存儲(chǔ)在堆上。因此新症,localVariable 2 的兩個(gè)副本最終都指向靜態(tài)變量所指向的 MySharedObject 的同一個(gè)實(shí)例步氏。MySharedObject 實(shí)例也存儲(chǔ)在堆中,它對(duì)應(yīng)于上圖中的對(duì)象 3徒爹。

注意 MySharedObject 類(lèi)也包含兩個(gè)成員變量荚醒。成員變量本身同對(duì)象一起存儲(chǔ)在堆中。這兩個(gè)成員變量指向另外兩個(gè) Integer 對(duì)象隆嗅,這些 Integer 對(duì)象對(duì)應(yīng)于上圖中的對(duì)象 2和對(duì)象 4界阁。

還要注意 methodTwo() 創(chuàng)建的一個(gè)名為 localVariable 1 的本地變量。這個(gè)局部變量是一個(gè)指向 Integer 對(duì)象的對(duì)象引用胖喳。該方法將 localVariable 1 引用設(shè)置為指向一個(gè)新的 Integer 實(shí)例泡躯。localVariable 1 引用將存儲(chǔ)在每個(gè)執(zhí)行 methodTwo() 的線程的一個(gè)副本中。實(shí)例化的兩個(gè) Integer 對(duì)象存儲(chǔ)在堆上,但是由于方法每次執(zhí)行都會(huì)創(chuàng)建一個(gè)新的 Integer 對(duì)象较剃,因此執(zhí)行該方法的兩個(gè)線程將創(chuàng)建單獨(dú)的 Integer 實(shí)例咕别。methodTwo() 中創(chuàng)建的 Integer 對(duì)象對(duì)應(yīng)于上圖中的對(duì)象 1和對(duì)象 5。還要注意類(lèi) MySharedObject 中的兩個(gè)成員變量写穴,它們的類(lèi)型是 long顷级,這是一個(gè)基本類(lèi)型。由于這些變量是成員變量确垫,所以它們?nèi)匀慌c對(duì)象一起存儲(chǔ)在堆中弓颈。只有本地變量存儲(chǔ)在線程堆棧中。

硬件內(nèi)存架構(gòu)

現(xiàn)代硬件內(nèi)存架構(gòu)與 Java 內(nèi)存模型略有不同删掀。了解硬件內(nèi)存架構(gòu)也很重要翔冀,以了解 Java 內(nèi)存模型如何與其一起工作。本節(jié)介紹了常見(jiàn)的硬件內(nèi)存架構(gòu)披泪,后面的部分將介紹 Java 內(nèi)存模型如何與其配合使用纤子。

這是現(xiàn)代計(jì)算機(jī)硬件架構(gòu)的簡(jiǎn)化圖:

現(xiàn)代計(jì)算機(jī)通常有兩個(gè)或更多的 CPU,其中一些 CPU 也可能有多個(gè)內(nèi)核款票。關(guān)鍵是控硼,在具有2個(gè)或更多 CPU 的現(xiàn)代計(jì)算機(jī)上,可以同時(shí)運(yùn)行多個(gè)線程艾少。每個(gè) CPU 都能夠在任何給定時(shí)間運(yùn)行一個(gè)線程卡乾。這意味著如果您的 Java 應(yīng)用程序是多線程的,那么每個(gè) CPU 可能同時(shí)(并發(fā)地)運(yùn)行 Java 應(yīng)用程序中的一個(gè)線程缚够。

每個(gè) CPU 包含一組寄存器幔妨,這些寄存器本質(zhì)上是在 CPU 內(nèi)存中。CPU 在這些寄存器上執(zhí)行操作的速度要比在主內(nèi)存中執(zhí)行變量的速度快得多谍椅。這是因?yàn)?CPU 訪問(wèn)這些寄存器的速度要比訪問(wèn)主內(nèi)存快得多误堡。

每個(gè) CPU 還可以有一個(gè) CPU 緩存內(nèi)存層。事實(shí)上雏吭,大多數(shù)現(xiàn)代 CPU 都有某種大小的緩存內(nèi)存層锁施。CPU 訪問(wèn)緩存內(nèi)存的速度比主內(nèi)存快得多,但通常沒(méi)有訪問(wèn)內(nèi)部寄存器的速度快杖们。因此悉抵,CPU 高速緩存存儲(chǔ)器介于內(nèi)部寄存器和主存儲(chǔ)器的速度之間。某些 CPU 可能有多個(gè)緩存層(L1 和 L2)胀莹,但要了解 Java 內(nèi)存模型如何與內(nèi)存交互基跑,這一點(diǎn)并不重要婚温。重要的是要知道 CPU 可以有某種緩存存儲(chǔ)層描焰。

計(jì)算機(jī)還包含一個(gè)主內(nèi)存區(qū)域(RAM)。所有 CPU 都可以訪問(wèn)主存,主內(nèi)存區(qū)域通常比 CPU 的緩存內(nèi)存大得多荆秦。

通常篱竭,當(dāng) CPU 需要訪問(wèn)主內(nèi)存時(shí),它會(huì)將部分主內(nèi)存讀入 CPU 緩存步绸。它甚至可以將緩存的一部分讀入內(nèi)部寄存器掺逼,然后對(duì)其執(zhí)行操作。當(dāng) CPU 需要將結(jié)果寫(xiě)回主內(nèi)存時(shí)瓤介,它會(huì)將值從內(nèi)部寄存器刷新到緩存內(nèi)存吕喘,并在某個(gè)時(shí)候?qū)⒅邓⑿禄刂鲀?nèi)存。

當(dāng)CPU需要在高速緩存中存儲(chǔ)其他內(nèi)容時(shí)刑桑,通常會(huì)將存儲(chǔ)在高速緩存中的值刷新回主內(nèi)存氯质。CPU 緩存可以一次將數(shù)據(jù)寫(xiě)入一部分內(nèi)存,并一次刷新一部分內(nèi)存祠斧。它不必每次更新時(shí)都讀取/寫(xiě)入完整的緩存闻察。通常,緩存是在稱(chēng)為“緩存線(Cache Line)”的較小內(nèi)存塊中更新的琢锋≡可以將一條或多條高速緩存線讀入高速緩存內(nèi)存,并將一條或多條高速緩存線再次刷新回主內(nèi)存吴超。

JMM 和硬件內(nèi)存結(jié)構(gòu)之間的差別

如前所述钉嘹,JMM 和硬件內(nèi)存結(jié)構(gòu)是不同的。硬件內(nèi)存體系結(jié)構(gòu)不區(qū)分線程棧和堆鲸阻。在硬件上隧期,線程棧和堆都位于主內(nèi)存中。線程棧和堆的一部分有時(shí)可能存在于 CPU 高速緩存和內(nèi)部 CPU 寄存器中赘娄。如下圖所示:

當(dāng)對(duì)象和變量可以存儲(chǔ)在計(jì)算機(jī)的不同內(nèi)存區(qū)域時(shí)仆潮,可能會(huì)出現(xiàn)某些問(wèn)題。主要有兩個(gè)問(wèn)題:

線程更新(寫(xiě)入)對(duì)共享變量的可見(jiàn)性

讀取遣臼、檢查和寫(xiě)入共享變量時(shí)的競(jìng)爭(zhēng)條件

這兩個(gè)問(wèn)題將在下面幾節(jié)中進(jìn)行解釋性置。

共享對(duì)象的可見(jiàn)性

如果兩個(gè)或多個(gè)線程共享一個(gè)對(duì)象,而沒(méi)有正確使用 volatile 聲明或同步揍堰,那么一個(gè)線程對(duì)共享對(duì)象的更新可能對(duì)其他線程不可見(jiàn)鹏浅。

假設(shè)共享對(duì)象最初存儲(chǔ)在主內(nèi)存中。在 CPU 1 上運(yùn)行的線程然后將共享對(duì)象讀入它的 CPU 緩存屏歹。在這里隐砸,它對(duì)共享對(duì)象進(jìn)行更改。只要沒(méi)有將 CPU 緩存刷新回主內(nèi)存蝙眶,在其他 CPU 上運(yùn)行的線程就不會(huì)看到共享對(duì)象的更改版本季希。這樣褪那,每個(gè)線程都可能最終擁有自己的共享對(duì)象副本,每個(gè)副本位于不同的 CPU緩 存中式塌。

下圖說(shuō)明了大致的情況博敬。在左 CPU 上運(yùn)行的一個(gè)線程將共享對(duì)象復(fù)制到其 CPU 緩存中,并將其 count 變量更改為2峰尝。此更改對(duì)運(yùn)行在正確 CPU 上的其他線程不可見(jiàn)偏窝,因?yàn)樯形磳⒏滤⑿禄刂鲀?nèi)存。

要解決這個(gè)問(wèn)題,可以使用 Java 的 volatile 關(guān)鍵字。volatile 關(guān)鍵字可以確保直接從主內(nèi)存讀取給定的變量荡含,并在更新時(shí)始終將其寫(xiě)回主內(nèi)存。

競(jìng)態(tài)條件

如果兩個(gè)或多個(gè)線程共享一個(gè)對(duì)象链沼,且多個(gè)線程更新該共享對(duì)象中的變量,則可能出現(xiàn)競(jìng)爭(zhēng)條件沛鸵。

假設(shè)線程 A 將共享對(duì)象的變量計(jì)數(shù)讀入其 CPU 緩存括勺。再想象一下,線程 B 執(zhí)行相同的操作曲掰,但是進(jìn)入了不同的 CPU 緩存〖埠矗現(xiàn)在線程 A 向 count 加一,線程 B 也這樣做±秆現(xiàn)在 var1 已經(jīng)增加了兩次乱豆,每次在每個(gè) CPU 緩存中增加一次。

如果按順序執(zhí)行這些增量吊趾,變量計(jì)數(shù)將增加兩次宛裕,并將原始值 + 2 寫(xiě)回主內(nèi)存。

但是论泛,這兩個(gè)增量是同時(shí)執(zhí)行的揩尸,沒(méi)有適當(dāng)?shù)耐健o(wú)論哪個(gè)線程 A 和線程 B 將其更新版本的 count 寫(xiě)回主內(nèi)存屁奏,更新后的值只比原始值高1岩榆,盡管有兩個(gè)增量。

該圖說(shuō)明了上述競(jìng)態(tài)條件問(wèn)題的發(fā)生情況:


要解決這個(gè)問(wèn)題坟瓢,可以使用 Java synchronized 塊勇边。同步塊保證在任何給定時(shí)間只有一個(gè)線程可以進(jìn)入代碼的給定臨界段。Synchronized 塊還保證在 Synchronized 塊中訪問(wèn)的所有變量都將從主內(nèi)存中讀入折联,當(dāng)線程退出 Synchronized 塊時(shí)粒褒,所有更新的變量將再次刷新回主內(nèi)存,而不管變量是否聲明為 volatile诚镰。

在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流群奕坟。交流學(xué)習(xí)群號(hào):833145934 里面資深架構(gòu)師會(huì)分享一些整理好的錄制視頻錄像和BATJ面試題:有Spring祥款,MyBatis,Netty源碼分析执赡,高并發(fā)镰踏、高性能函筋、分布式沙合、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化跌帐、分布式架構(gòu)等這些成為架構(gòu)師必備的知識(shí)體系首懈。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源,目前受益良多谨敛。

注:本文轉(zhuǎn)載自 linkedkeeper.com (文/張松然)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末究履,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子脸狸,更是在濱河造成了極大的恐慌最仑,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炊甲,死亡現(xiàn)場(chǎng)離奇詭異泥彤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)卿啡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)吟吝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人颈娜,你說(shuō)我怎么就攤上這事剑逃。” “怎么了官辽?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵蛹磺,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我同仆,道長(zhǎng)称开,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任乓梨,我火速辦了婚禮鳖轰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扶镀。我一直安慰自己蕴侣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布臭觉。 她就那樣靜靜地躺著昆雀,像睡著了一般辱志。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上狞膘,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天揩懒,我揣著相機(jī)與錄音,去河邊找鬼挽封。 笑死已球,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的辅愿。 我是一名探鬼主播智亮,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼点待!你這毒婦竟也來(lái)了阔蛉?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤癞埠,失蹤者是張志新(化名)和其女友劉穎状原,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體苗踪,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡颠区,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了徒探。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓦呼。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖测暗,靈堂內(nèi)的尸體忽然破棺而出央串,到底是詐尸還是另有隱情,我是刑警寧澤碗啄,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布质和,位于F島的核電站,受9級(jí)特大地震影響稚字,放射性物質(zhì)發(fā)生泄漏饲宿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一胆描、第九天 我趴在偏房一處隱蔽的房頂上張望瘫想。 院中可真熱鬧,春花似錦昌讲、人聲如沸国夜。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)车吹。三九已至筹裕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間窄驹,已是汗流浹背朝卒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乐埠,地道東北人抗斤。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像饮戳,于是被迫代替她去往敵國(guó)和親豪治。 傳聞我的和親對(duì)象是個(gè)殘疾皇子洞拨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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