synchronized 鎖優(yōu)化和鎖升級過程

一扇商、synchronized鎖優(yōu)化

高效并發(fā)是從JDK 5升級到JDK 6后一項重要的改進項,HotSpot虛擬機開發(fā)團隊在這個版本上花費了大量的資源去實現(xiàn)各種鎖優(yōu)化技術(shù)絮重,如適應(yīng)性自旋(Adaptive Spinning)仙畦、鎖消除(Lock Elimination)雾叭、鎖膨脹(Lock Coarsening)、輕量級鎖(Lightweight Locking)磅废、偏向鎖(Biased Locking)等题翰,這些技術(shù)都是為了在線程之間更高效地共享數(shù)據(jù)及解決競爭問題,從而提高程序的執(zhí)行效率窖张。

1幕随、自旋鎖與自適應(yīng)自旋

前面介紹線程時提到了掛起線程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成,頻繁的用戶態(tài)荤堪、內(nèi)核態(tài)切換是非常消耗資源的合陵。有時候一個線程獲取鎖之后很短時間就能執(zhí)行完畢,為了這段時間去掛起和恢復(fù)線程并不值得澄阳,可以讓后面還未獲取鎖的線程自己等會一會兒而不讓出CPU執(zhí)行時間拥知,看看持有鎖的線程是否很快就會釋放鎖。為了讓線程等待碎赢,我們只須讓線程執(zhí)行一個忙循環(huán)(自旋)低剔,這項技術(shù)就是所謂的自旋鎖。

自旋鎖在JDK 1.4.2中就已經(jīng)引入肮塞,只不過默認是關(guān)閉的襟齿,可以使用-XX:+UseSpinning參數(shù)來開啟,在JDK 6中就已經(jīng)改為默認開啟了枕赵。自旋等待不能代替阻塞猜欺,且先不說對處理器數(shù)量的要求,自旋等待本身雖然避免了線程切換的開銷拷窜,但它是要占用處理器時間的开皿,所以如果鎖被占用的時間很短涧黄,自旋等待的效果就會非常好,反之如果鎖被占用的時間很長赋荆,那么自旋的線程只會白白消耗處理器資源笋妥,這就會帶來性能的浪費。因此自旋等待的時間必須有一定的限度窄潭,如果自旋超過了限定的次數(shù)仍然沒有成功獲得鎖春宣,就應(yīng)當使用傳統(tǒng)的方式去掛起線程。自旋次數(shù)的默認值是十次嫉你,用戶也可以使用參數(shù)-XX:PreBlockSpin來自行更改月帝。

在JDK 6中對自旋鎖的優(yōu)化,引入了自適應(yīng)的自旋均抽。自適應(yīng)意味著自旋的時間不再是固定的了嫁赏,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態(tài)來決定的。如果在同一個鎖對象上油挥,自旋等待剛剛成功獲得過鎖潦蝇,并且持有鎖的線程正在運行中,那么虛擬機就會認為這次自旋也很有可能再次成功深寥,進而允許自旋等待持續(xù)相對更長的時間攘乒,比如持續(xù)100次忙循環(huán)。另一方面惋鹅,如果對于某個鎖则酝,自旋很少成功獲得過鎖,那在以后要獲取這個鎖時將有可能直接省略掉自旋過程闰集,以避免浪費處理器資源沽讹。

2、鎖消除

消除鎖是虛擬機另外一種鎖的優(yōu)化武鲁,這種優(yōu)化更徹底爽雄,Java虛擬機在JIT編譯時(可以簡單理解為當某段代碼即將第一次被執(zhí)行時進行編譯,又稱即時編譯)沐鼠,通過對運行上下文的掃描挚瘟,去除不可能存在共享資源競爭的鎖,通過這種方式消除沒有必要的鎖饲梭,可以節(jié)省毫無意義的請求鎖時間乘盖,如下StringBuffer的append是一個同步方法,但是在add方法中的StringBuffer屬于一個局部變量憔涉,并且不會被其他線程所使用订框,因此StringBuffer不可能存在共享資源競爭的情景,JVM會自動將其鎖消除兜叨。

public String concatString(String s1, String s2, String s3) {    StringBuffer sb = new StringBuffer();    sb.append(s1);    sb.append(s2);    sb.append(s3);    return sb.toString();}

逃逸分析:

使用逃逸分析布蔗,編譯器可以對代碼做如下優(yōu)化:

  • 一藤违、同步省略。如果一個對象被發(fā)現(xiàn)只能從一個線程被訪問到纵揍,那么對于這個對象的操作可以不考慮同步。

  • 二议街、將堆分配轉(zhuǎn)化為棧分配泽谨。如果一個對象在子程序中被分配,要使指向該對象的指針永遠不會逃逸特漩,對象可能是棧分配的候選吧雹,而不是堆分配。

  • 三涂身、分離對象或標量替換雄卷。有的對象可能不需要作為一個連續(xù)的內(nèi)存結(jié)構(gòu)存在也可以被訪問到,那么對象的部分(或全部)可以不存儲在內(nèi)存蛤售,而是存儲在CPU寄存器中丁鹉。

是不是所有的對象和數(shù)組都會在堆內(nèi)存分配空間?不一定悴能。在Java代碼運行時揣钦,通過JVM參數(shù)可指定是否開啟逃逸分析, XX:+DoEscapeAnalysis : 表示開啟逃逸分析 XX:-DoEscapeAnalysis: 表示關(guān)閉逃逸分析漠酿。從jdk 1.7開始已經(jīng)默認開啟逃逸分析冯凹,開啟之后可以通過參數(shù)-XX:+PrintEscapeAnalysis來查看分析結(jié)果。有了逃逸分析支持之后炒嘲,用戶可以使用參數(shù)-XX:+EliminateAllocations來開啟標量替換宇姚,使用+XX:+EliminateLocks來開啟同步消除,使用參數(shù)-XX:+PrintEliminateAllocations查看標量的替換情況夫凸。

通過下面的代碼示例演示一下開啟逃逸分析后浑劳,對象進行棧上分配的情況:運行時通過jps查看程序的進程ID,然后通過jmap -histo 進程ID查看Student對象的數(shù)量寸痢,可以觀察到在關(guān)閉逃逸分析的時候?qū)ο蟮臄?shù)量是50萬呀洲,而開啟逃逸分析后是小于50萬的,說明有Student對象是在棧上分配的而不是堆上分配的啼止。

public class StackAllocTest {     /**     * 進行兩種測試     * 關(guān)閉逃逸分析道逗,同時調(diào)大堆空間,避免堆內(nèi)GC的發(fā)生献烦,如果有GC信息將會被打印出來     * VM運行參數(shù):-Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError     *     * 開啟逃逸分析     * VM運行參數(shù):-Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError     *     * 執(zhí)行main方法后     * jps 查看進程     * jmap -histo 進程ID     *     */     public static void main(String[] args) {        long start = System.currentTimeMillis();        for (int i = 0; i < 500000; i++) {            alloc();        }        long end = System.currentTimeMillis();        //查看執(zhí)行時間        System.out.println("cost-time " + (end - start) + " ms");        try {            Thread.sleep(100000);        } catch (InterruptedException e1) {            e1.printStackTrace();        }    }      private static Student alloc() {        //Jit對編譯時會對代碼進行 逃逸分析        //并不是所有對象存放在堆區(qū)滓窍,有的一部分存在線程棧空間        Student student = new Student();        return student;    }     static class Student {        private String name;        private int age;    }}

3巩那、鎖粗化

原則上吏夯,我們在編寫代碼的時候此蜈,總是推薦將同步塊的作用范圍限制得盡量小。大多數(shù)情況下噪生,上面的原則都是正確的裆赵,但是如果一系列的連續(xù)操作都對同一個對象反復(fù)加鎖和解鎖,甚至加鎖操作是出現(xiàn)在循環(huán)體之中的跺嗽,那即使沒有線程競爭战授,頻繁地進行互斥同步操作也會導(dǎo)致不必要的性能損耗。

上面的代碼示例中所示StringBuffer連續(xù)的append()方法就屬于這類情況桨嫁。如果虛擬機探測到有這樣一串零碎的操作都對同一個對象加鎖植兰,將會把加鎖同步的范圍擴展(粗化)到整個操作序列的外部,以上面代碼為例璃吧,就是擴展到第一個append()操作之前直至最后一個append()操作之后楣导,這樣只需要加鎖一次就可以了。

二畜挨、對象頭內(nèi)存布局

我們知道synchronized加鎖加在對象上筒繁,對象是如何記錄鎖狀態(tài)的呢?答案是鎖狀態(tài)是被記錄在每個對象的對象頭(Mark Word)中朦促,下面我們一起認識一下對象的內(nèi)存布局膝晾。關(guān)于對象的內(nèi)存布局,可以先看下面這張圖务冕,圖中已經(jīng)畫的很清楚了血当,可以看到內(nèi)存中存儲的區(qū)域可以分為三部分:對象頭(Header),實例數(shù)據(jù)(Instance Data)和對齊填充(Padding)禀忆。

image

對象頭包括兩部分信息臊旭,第一部分用于存儲對象自身的運行時數(shù)據(jù)(也稱為"MarkWord"),如哈希碼(HashCode)箩退、GC分代年齡离熏、鎖狀態(tài)標志、線程持有的鎖戴涝、偏向線程ID滋戳、偏向時間戳等,這部分數(shù)據(jù)的長度在32位和64位的虛擬機(未開啟壓縮指針)中分別為32bit和64bit啥刻。MarkWord被設(shè)計成一個非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小的空間內(nèi)存儲盡量多的信息奸鸯,它會根據(jù)對象的狀態(tài)復(fù)用自己的存儲空間。例如可帽,在32位的Hotspot虛擬機中娄涩,如果對象處于未被鎖定的狀態(tài)下,那么MarkWord的32bit空間中的25bit用于存儲對象哈希碼映跟,4bit用于存儲對象分代年齡蓄拣,2bit用于存儲鎖標志位扬虚,1bit固定為0,而在其他狀態(tài)(輕量級鎖定球恤、重量級鎖定辜昵、GC標記、可偏向)下對象的存儲內(nèi)容如下所示:

image

在OpenJDK源碼中openjdk\hotspot\src\share\vm\oops目錄下有個markOop.cpp碎捺,里面定義了對象頭MarkWork中的存儲內(nèi)容路鹰,有興趣的可以看一下。

三收厨、synchronized鎖的膨脹升級過程

鎖的狀態(tài)總共有四種,無鎖狀態(tài)优构、偏向鎖诵叁、輕量級鎖和重量級鎖。隨著鎖的競爭钦椭,鎖可以從偏向鎖升級到輕量級鎖拧额,再升級的重量級鎖,但是鎖的升級是單向的彪腔,也就是說只能從低到高升級侥锦,不會出現(xiàn)鎖的降級。

image

1德挣、偏向鎖

偏向鎖也是JDK 6中引入的一項鎖優(yōu)化措施恭垦,它的目的是消除數(shù)據(jù)在無競爭情況下的同步原語,進一步提高程序的運行性能格嗅。偏向鎖的意思是這個鎖會偏向于第一個獲得它的線程番挺,如果在接下來的執(zhí)行過程中,該鎖一直沒有被其他的線程獲取屯掖,則持有偏向鎖的線程將永遠不需要再進行同步玄柏。

假設(shè)當前虛擬機啟用了偏向鎖(啟用參數(shù)-XX:+UseBiased Locking,這是自JDK 6起HotSpot虛擬機的默認值)贴铜,那么當鎖對象第一次被線程獲取的時候粪摘,虛擬機將會把對象頭中的標志位設(shè)置為“01”、把偏向模式設(shè)置為“1”绍坝,表示進入偏向模式徘意。同時使用CAS操作把獲取到這個鎖的線程的ID記錄在對象的Mark Word之中。如果CAS操作成功陷嘴,持有偏向鎖的線程以后每次進入這個鎖相關(guān)的同步塊時映砖,虛擬機都可以不再進行任何同步操作(例如加鎖、解鎖及對Mark Word的更新操作等)灾挨。

一旦出現(xiàn)另外一個線程去嘗試獲取這個鎖的情況邑退,偏向模式就馬上宣告結(jié)束竹宋。根據(jù)鎖對象目前是否處于被鎖定的狀態(tài)決定是否撤銷偏向(偏向模式設(shè)置為“0”),撤銷后標志位恢復(fù)到未鎖定(標志位為“01”)或輕量級鎖定(標志位為“00”)的狀態(tài)地技,后續(xù)的同步操作就按照輕量級鎖那樣去執(zhí)行蜈七。

image

偏向鎖使用了一種等到競爭出現(xiàn)才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時莫矗,持有偏向鎖的線程才會釋放鎖飒硅。偏向鎖的撤銷,需要等待全局安全點(在這個時間上沒有正在執(zhí)行的字節(jié)碼)作谚。它會首先暫停擁有偏向鎖的線程三娩,然后檢查持有偏向鎖的線程是否活著,如果線程同步塊已經(jīng)執(zhí)行完妹懒,則將對象頭設(shè)置成無鎖狀態(tài)雀监;如果線程同步塊還沒執(zhí)行完,需要將偏向鎖升級為輕量級鎖眨唬。

當對象進入偏向狀態(tài)的時候会前,Mark Word大部分的空間(23個比特)都用于存儲持有鎖的線程ID了,這部分空間占用了原有存儲對象哈希碼的位置匾竿,那原來對象的哈希碼怎么辦呢瓦宜?

在Java語言里面一個對象如果計算過哈希碼,就應(yīng)該一直保持該值不變(強烈推薦但不強制岭妖,因為用戶可以重載hashCode()方法按自己的意愿返回哈希碼)临庇,否則很多依賴對象哈希碼的API都可能存在出錯風險。而作為絕大多數(shù)對象哈希碼來源的Object::hashCode()方法区转,返回的是對象的一致性哈希碼(Identity Hash Code)苔巨,這個值是能強制保證不變的,它通過在對象頭中存儲計算結(jié)果來保證第一次計算之后废离,再次調(diào)用該方法取到的哈希碼值永遠不會再發(fā)生改變侄泽。因此,當一個對象已經(jīng)計算過一致性哈希碼后蜻韭,它就再也無法進入偏向鎖狀態(tài)了悼尾;而當一個對象當前正處于偏向鎖狀態(tài),又收到需要計算其一致性哈希碼請求(這里說的計算請求應(yīng)來自于對Object::hashCode()或者System::identityHashCode(Object)方法的調(diào)用肖方,如果重寫了對象的hashCode()方法闺魏,計算哈希碼時并不會產(chǎn)生這里所說的請求)時,它的偏向狀態(tài)會被立即撤銷俯画,并且鎖會膨脹為重量級鎖析桥。在重量級鎖的實現(xiàn)中,對象頭指向了重量級鎖的位置,代表重量級鎖的ObjectMonitor類里有字段可以記錄非加鎖狀態(tài)(標志位為“01”)下的Mark Word泡仗,其中自然可以存儲原來的哈希碼埋虹。

2、輕量級鎖

倘若偏向鎖失敗娩怎,虛擬機并不會立即升級為重量級鎖搔课,它還會嘗試使用一種稱為輕量級鎖的優(yōu)化手段(1.6之后加入的),此時Mark Word 的結(jié)構(gòu)也變?yōu)檩p量級鎖的結(jié)構(gòu)截亦。輕量級鎖能夠提升程序性能的依據(jù)是“對絕大部分的鎖爬泥,在整個同步周期內(nèi)都不存在競爭”,注意這是經(jīng)驗數(shù)據(jù)崩瓤。需要了解的是袍啡,輕量級鎖所適應(yīng)的場景是線程交替執(zhí)行同步塊的場合,如果存在同一時間訪問同一鎖的場合却桶,就會導(dǎo)致輕量級鎖膨脹為重量級鎖葬馋。

在代碼即將進入同步塊的時候,如果此同步對象沒有被鎖定(鎖標志位為“01”狀態(tài))肾扰,虛擬機首先將在當前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用于存儲鎖對象目前的Mark Word的拷貝(官方為這份拷貝加了一個Displaced前綴蛋逾,即Displaced Mark Word)集晚,這時候線程堆棧與對象頭的狀態(tài)如下圖所示。

image

然后区匣,虛擬機將使用CAS操作嘗試把對象的Mark Word更新為指向Lock Record的指針偷拔。如果這個更新動作成功了,即代表該線程擁有了這個對象的鎖亏钩,并且對象Mark Word的鎖標志位(Mark Word的最后兩個比特)將轉(zhuǎn)變?yōu)椤?0”莲绰,表示此對象處于輕量級鎖定狀態(tài)。這時候線程堆棧與對象頭的狀態(tài)如下圖所示姑丑。

image

如果這個更新操作失敗了蛤签,那就意味著至少存在一條線程與當前線程競爭獲取該對象的鎖。虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀栅哀,如果是震肮,說明當前線程已經(jīng)擁有了這個對象的鎖,那直接進入同步塊繼續(xù)執(zhí)行就可以了留拾,否則就說明這個鎖對象已經(jīng)被其他線程搶占了戳晌。如果出現(xiàn)兩條以上的線程爭用同一個鎖的情況,那輕量級鎖就不再有效痴柔,必須要膨脹為重量級鎖沦偎,鎖標志的狀態(tài)值變?yōu)椤?0”,此時Mark Word中存儲的就是指向重量級鎖(互斥量)的指針,后面等待鎖的線程也必須進入阻塞狀態(tài)豪嚎。

上面描述的是輕量級鎖的加鎖過程搔驼,它的解鎖過程也同樣是通過CAS操作來進行的,如果對象的Mark Word仍然指向線程的鎖記錄疙渣,那就用CAS操作把對象當前的Mark Word和線程中復(fù)制的Displaced Mark Word替換回來匙奴。假如能夠成功替換,那整個同步過程就順利完成了妄荔;如果替換失敗泼菌,則說明有其他線程嘗試過獲取該鎖,就要在釋放鎖的同時啦租,喚醒被掛起的線程哗伯。

輕量級鎖能提升程序同步性能的依據(jù)是“對于絕大部分的鎖,在整個同步周期內(nèi)都是不存在競爭的”這一經(jīng)驗法則篷角。如果沒有競爭焊刹,輕量級鎖便通過CAS操作成功避免了使用互斥量的開銷;但如果確實存在鎖競爭恳蹲,除了互斥量的本身開銷外虐块,還額外發(fā)生了CAS操作的開銷。因此在有競爭的情況下嘉蕾,輕量級鎖反而會比傳統(tǒng)的重量級鎖更慢贺奠。

3、重量級鎖

當鎖升級到重量級鎖時错忱,就用到了上文提到的ObjectMonitor(監(jiān)視器鎖)儡率。在hotspot源碼的markOop.hpp文件中,可以看到下面這段代碼以清。多個線程訪問同步代碼塊時儿普,相當于去爭搶對象監(jiān)視器修改對象中的鎖標識。對象頭MarkWord中可以通過monitor()方法獲取ObjectMonitor的指針掷倔,也就是前面以32位虛擬機舉例時MarkWord前30位變成了指向ObjectMonitor的指針眉孩。

  bool has_monitor() const {    return ((value() & monitor_value) != 0);  }  ObjectMonitor* monitor() const {    assert(has_monitor(), "check");    // Use xor instead of &~ to provide one extra tag-bit check.    return (ObjectMonitor*) (value() ^ monitor_value);  }

4、各種鎖的優(yōu)缺點

優(yōu)點 缺點 適用場景
偏向鎖 加鎖和解鎖不需要額外的消耗今魔,和執(zhí)行非同步方法相比僅存在納秒級的差距 如果線程間存在鎖競爭勺像,會帶來額外的鎖撤銷的消耗 適用于只有一個線程訪問同步塊的場景
輕量級鎖 競爭的線程不會阻塞,提高了程序的響應(yīng)速度 如果始終得不到鎖競爭的線程错森,使用自旋會消耗CPU 追求響應(yīng)時間吟宦,同步塊執(zhí)行速度非常快涩维,多個線程交替執(zhí)行的場景
重量級鎖 線程競爭不適用自旋殃姓,不會消耗CPU 線程阻塞袁波,響應(yīng)時間緩慢 追求吞吐量,同步塊執(zhí)行時間較長蜗侈,多個線程鎖競爭激烈的場景
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末篷牌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子踏幻,更是在濱河造成了極大的恐慌枷颊,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件该面,死亡現(xiàn)場離奇詭異夭苗,居然都是意外死亡,警方通過查閱死者的電腦和手機隔缀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門题造,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人猾瘸,你說我怎么就攤上這事界赔。” “怎么了牵触?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵淮悼,是天一觀的道長。 經(jīng)常有香客問我揽思,道長敛惊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任绰更,我火速辦了婚禮,結(jié)果婚禮上锡宋,老公的妹妹穿的比我還像新娘儡湾。我一直安慰自己,他們只是感情好执俩,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布徐钠。 她就那樣靜靜地躺著,像睡著了一般役首。 火紅的嫁衣襯著肌膚如雪尝丐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天衡奥,我揣著相機與錄音爹袁,去河邊找鬼。 笑死矮固,一個胖子當著我的面吹牛失息,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盹兢,長吁一口氣:“原來是場噩夢啊……” “哼邻梆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起绎秒,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤浦妄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后见芹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剂娄,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年辆童,在試婚紗的時候發(fā)現(xiàn)自己被綠了宜咒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡把鉴,死狀恐怖故黑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情庭砍,我是刑警寧澤场晶,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站怠缸,受9級特大地震影響诗轻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜揭北,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一扳炬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧搔体,春花似錦恨樟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至呆奕,卻和暖如春养晋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梁钾。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工销睁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留仔拟,地道東北人盟萨。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像秦忿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蛾娶,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

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