(轉(zhuǎn)載)Java并發(fā)編程-無鎖CAS與Unsafe類

原文鏈接:Java并發(fā)編程-無鎖CAS與Unsafe類及其并發(fā)包Atomic - CSDN博客

在前面一篇博文中捺宗,我們曾經(jīng)詳談過有鎖并發(fā)的典型代表synchronized關(guān)鍵字涛贯,通過該關(guān)鍵字可以控制并發(fā)執(zhí)行過程中有且只有一個線程可以訪問共享資源,其原理是通過當(dāng)前線程持有當(dāng)前對象鎖钞钙,從而擁有訪問權(quán)限熄浓,而其他沒有持有當(dāng)前對象鎖的線程無法擁有訪問權(quán)限校坑,也就保證了線程安全闯团。但在本篇中,我們將會詳聊另外一種反向而行的并發(fā)策略疹味,即無鎖并發(fā)仅叫,即不加鎖也能保證并發(fā)執(zhí)行的安全性。?

本篇的思路是先闡明無鎖執(zhí)行者CAS的核心算法原理然后分析Java執(zhí)行CAS的實(shí)踐者Unsafe類糙捺,該類中的方法都是native修飾的诫咱,因此我們會以說明方法作用為主介紹Unsafe類,最后再介紹并發(fā)包中的Atomic系統(tǒng)使用CAS原理實(shí)現(xiàn)的并發(fā)類洪灯。

無鎖的概念

在談?wù)摕o鎖概念時坎缭,總會關(guān)聯(lián)起樂觀派與悲觀派,對于樂觀派而言签钩,他們認(rèn)為事情總會往好的方向發(fā)展掏呼,總是認(rèn)為壞的情況發(fā)生的概率特別小,可以無所顧忌地做事铅檩,但對于悲觀派而已憎夷,他們總會認(rèn)為發(fā)展事態(tài)如果不及時控制,以后就無法挽回了昧旨,即使無法挽回的局面幾乎不可能發(fā)生拾给。這兩種派系映射到并發(fā)編程中就如同加鎖與無鎖的策略祥得,即加鎖是一種悲觀策略,無鎖是一種樂觀策略蒋得,因?yàn)閷τ诩渔i的并發(fā)程序來說级及,它們總是認(rèn)為每次訪問共享資源時總會發(fā)生沖突,因此必須對每一次數(shù)據(jù)操作實(shí)施加鎖策略额衙。而無鎖則總是假設(shè)對共享資源的訪問沒有沖突饮焦,線程可以不停執(zhí)行,無需加鎖窍侧,無需等待县踢,一旦發(fā)現(xiàn)沖突,無鎖策略則采用一種稱為CAS的技術(shù)來保證線程執(zhí)行的安全性疏之,這項(xiàng)CAS技術(shù)就是無鎖策略實(shí)現(xiàn)的關(guān)鍵,下面我們進(jìn)一步了解CAS技術(shù)的奇妙之處暇咆。

無鎖的執(zhí)行者-CAS

CAS

CAS的全稱是Compare And Swap 即比較交換锋爪,其算法核心思想如下:

執(zhí)行函數(shù):CAS(V,E,N)

其包含3個參數(shù)

1. V表示要更新的變量

2. E表示預(yù)期值

3. N表示新值

如果V值等于E值,則將V的值設(shè)為N爸业。若V值和E值不同其骄,則說明已經(jīng)有其他線程做了更新,則當(dāng)前線程什么都不做扯旷。通俗的理解就是CAS操作需要我們提供一個期望值拯爽,當(dāng)期望值與當(dāng)前線程的變量值相同時,說明還沒線程修改該值钧忽,當(dāng)前線程可以進(jìn)行修改毯炮,也就是執(zhí)行CAS操作,但如果期望值與當(dāng)前線程不符耸黑,則說明該值已被其他線程修改桃煎,此時不執(zhí)行更新操作,但可以選擇重新讀取該變量再嘗試再次修改該變量大刊,也可以放棄操作为迈,原理圖如下:

由于CAS操作屬于樂觀派,它總認(rèn)為自己可以成功完成操作缺菌,當(dāng)多個線程同時使用CAS操作一個變量時葫辐,只有一個會勝出,并成功更新伴郁,其余均會失敗耿战,但失敗的線程并不會被掛起,僅是被告知失敗焊傅,并且允許再次嘗試昆箕,當(dāng)然也允許失敗的線程放棄操作鸦列,這點(diǎn)從圖中也可以看出來∨籼龋基于這樣的原理薯嗤,CAS操作即使沒有鎖,同樣知道其他線程對共享資源操作影響纤泵,并執(zhí)行相應(yīng)的處理措施骆姐。同時從這點(diǎn)也可以看出,由于無鎖操作中沒有鎖的存在捏题,因此不可能出現(xiàn)死鎖的情況玻褪,也就是說無鎖操作天生免疫死鎖。

CPU指令對CAS的支持

或許我們可能會有這樣的疑問公荧,假設(shè)存在多個線程執(zhí)行CAS操作并且CAS的步驟很多带射,有沒有可能在判斷V和E相同后,正要賦值時循狰,切換了線程窟社,更改了值。造成了數(shù)據(jù)不一致呢绪钥?答案是否定的灿里,因?yàn)镃AS是一種系統(tǒng)原語,原語屬于操作系統(tǒng)用語范疇程腹,是由若干條指令組成的匣吊,用于完成某個功能的一個過程,并且原語的執(zhí)行必須是連續(xù)的寸潦,在執(zhí)行過程中不允許被中斷色鸳,也就是說CAS是一條CPU的原子指令,不會造成所謂的數(shù)據(jù)不一致問題见转。

鮮為人知的指針: Unsafe類

Unsafe類存在于sun.misc包中缕碎,其內(nèi)部方法操作可以像C的指針一樣直接操作內(nèi)存,單從名稱看來就可以知道該類是非安全的池户,畢竟Unsafe擁有著類似于C的指針操作咏雌,因此總是不應(yīng)該首先使用Unsafe類,Java官方也不建議直接使用的Unsafe類校焦,據(jù)說Oracle正在計(jì)劃從Java 9中去掉Unsafe類赊抖,但我們還是很有必要了解該類,因?yàn)镴ava中CAS操作的執(zhí)行依賴于Unsafe類的方法寨典,注意Unsafe類中的所有方法都是native修飾的氛雪,也就是說Unsafe類中的方法都直接調(diào)用操作系統(tǒng)底層資源執(zhí)行相應(yīng)任務(wù),關(guān)于Unsafe類的主要功能點(diǎn)如下:

內(nèi)存管理耸成,Unsafe類中存在直接操作內(nèi)存的方法

//分配內(nèi)存指定大小的內(nèi)存

public native long allocateMemory(long bytes);

//根據(jù)給定的內(nèi)存地址address設(shè)置重新分配指定大小的內(nèi)存

public native long reallocateMemory(long address, long bytes);

//用于釋放allocateMemory和reallocateMemory申請的內(nèi)存

public native void freeMemory(long address);

//將指定對象的給定offset偏移量內(nèi)存塊中的所有字節(jié)設(shè)置為固定值

public native void setMemory(Object o, long offset, long bytes, byte value);

//設(shè)置給定內(nèi)存地址的值

public native void putAddress(long address, long x);

//獲取指定內(nèi)存地址的值

public native long getAddress(long address);

//設(shè)置給定內(nèi)存地址的long值

public native void putLong(long address, long x);

//獲取指定內(nèi)存地址的long值

public native long getLong(long address);

//設(shè)置或獲取指定內(nèi)存的byte值

public native byte getByte(long address);

public native void putByte(long address, byte x);

//其他基本數(shù)據(jù)類型(long,char,float,double,short等)的操作與putByte及getByte相同

//操作系統(tǒng)的內(nèi)存頁大小

public native int pageSize();

提供實(shí)例對象新途徑报亩。

//傳入一個對象的class并創(chuàng)建該實(shí)例對象浴鸿,但不會調(diào)用構(gòu)造方法

public native Object allocateInstance(Class cls) throws InstantiationException;

類和實(shí)例對象以及變量的操作,主要方法如下:

//獲取字段f在實(shí)例對象中的偏移量

public native long objectFieldOffset(Field f);

//靜態(tài)屬性的偏移量弦追,用于在對應(yīng)的Class對象中讀寫靜態(tài)屬性

public native long staticFieldOffset(Field f);

//返回值就是f.getDeclaringClass()

public native Object staticFieldBase(Field f);

//獲得給定對象偏移量上的int值岳链,所謂的偏移量可以簡單理解為指針指向該變量的內(nèi)存地址,

//通過偏移量便可得到該對象的變量劲件,進(jìn)行各種操作

public native int getInt(Object o, long offset);

//設(shè)置給定對象上偏移量的int值

public native void putInt(Object o, long offset, int x);

//獲得給定對象偏移量上的引用類型的值

public native Object getObject(Object o, long offset);

//設(shè)置給定對象偏移量上的引用類型的值

public native void putObject(Object o, long offset, Object x);

//其他基本數(shù)據(jù)類型(long,char,byte,float,double)的操作與getInthe及putInt相同

//設(shè)置給定對象的int值掸哑,使用volatile語義,即設(shè)置后立馬更新到內(nèi)存對其他線程可見

public native void putIntVolatile(Object o, long offset, int x);

//獲得給定對象的指定偏移量offset的int值零远,使用volatile語義苗分,總能獲取到最新的int值。

public native int getIntVolatile(Object o, long offset);

//其他基本數(shù)據(jù)類型(long,char,byte,float,double)的操作與putIntVolatile及getIntVolatile相同牵辣,引用類型putObjectVolatile也一樣摔癣。

//與putIntVolatile一樣,但要求被操作字段必須有volatile修飾

public native void putOrderedInt(Object o,long offset,int x);

下面通過一個簡單的Demo來演示上述的一些方法以便加深對Unsafe類的理解:

public class UnSafeDemo {

? ? public? static? void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {

? ? ? ? // 通過反射得到theUnsafe對應(yīng)的Field對象

? ? ? ? Field field = Unsafe.class.getDeclaredField("theUnsafe");

? ? ? ? // 設(shè)置該Field為可訪問

? ? ? ? field.setAccessible(true);

? ? ? ? // 通過Field得到該Field對應(yīng)的具體對象纬向,傳入null是因?yàn)樵揊ield為static的

? ? ? ? Unsafe unsafe = (Unsafe) field.get(null);

? ? ? ? System.out.println(unsafe);

? ? ? ? //通過allocateInstance直接創(chuàng)建對象

? ? ? ? User user = (User) unsafe.allocateInstance(User.class);

? ? ? ? Class userClass = user.getClass();

? ? ? ? Field name = userClass.getDeclaredField("name");

? ? ? ? Field age = userClass.getDeclaredField("age");

? ? ? ? Field id = userClass.getDeclaredField("id");

? ? ? ? //獲取實(shí)例變量name和age在對象內(nèi)存中的偏移量并設(shè)置值

? ? ? ? unsafe.putInt(user,unsafe.objectFieldOffset(age),18);

? ? ? ? unsafe.putObject(user,unsafe.objectFieldOffset(name),"android TV");

? ? ? ? // 這里返回 User.class择浊,

? ? ? ? Object staticBase = unsafe.staticFieldBase(id);

? ? ? ? System.out.println("staticBase:"+staticBase);

? ? ? ? //獲取靜態(tài)變量id的偏移量staticOffset

? ? ? ? long staticOffset = unsafe.staticFieldOffset(userClass.getDeclaredField("id"));

? ? ? ? //獲取靜態(tài)變量的值

? ? ? ? System.out.println("設(shè)置前的ID:"+unsafe.getObject(staticBase,staticOffset));

? ? ? ? //設(shè)置值

? ? ? ? unsafe.putObject(staticBase,staticOffset,"SSSSSSSS");

? ? ? ? //獲取靜態(tài)變量的值

? ? ? ? System.out.println("設(shè)置前的ID:"+unsafe.getObject(staticBase,staticOffset));

? ? ? ? //輸出USER

? ? ? ? System.out.println("輸出USER:"+user.toString());

? ? ? ? long data = 1000;

? ? ? ? byte size = 1;//單位字節(jié)

? ? ? ? //調(diào)用allocateMemory分配內(nèi)存,并獲取內(nèi)存地址memoryAddress

? ? ? ? long memoryAddress = unsafe.allocateMemory(size);

? ? ? ? //直接往內(nèi)存寫入數(shù)據(jù)

? ? ? ? unsafe.putAddress(memoryAddress, data);

? ? ? ? //獲取指定內(nèi)存地址的數(shù)據(jù)

? ? ? ? long addrData=unsafe.getAddress(memoryAddress);

? ? ? ? System.out.println("addrData:"+addrData);

? ? ? ? /**

? ? ? ? * 輸出結(jié)果:

? ? ? ? sun.misc.Unsafe@6f94fa3e

? ? ? ? staticBase:class geym.conc.ch4.atomic.User

? ? ? ? 設(shè)置前的ID:USER_ID

? ? ? ? 設(shè)置前的ID:SSSSSSSS

? ? ? ? 輸出USER:User{name='android TV', age=18', id=SSSSSSSS'}

? ? ? ? addrData:1000

? ? ? ? */? ? }

}

class User{

? ? public User(){

? ? ? ? System.out.println("user 構(gòu)造方法被調(diào)用");

? ? }

? ? private String name;

? ? private int age;

? ? private static String id="USER_ID";

? ? @Override

? ? public String toString() {

? ? ? ? return "User{" +

? ? ? ? ? ? ? ? "name='" + name + '\'' +

? ? ? ? ? ? ? ? ", age=" + age +'\'' +

? ? ? ? ? ? ? ? ", id=" + id +'\'' +

? ? ? ? ? ? ? ? '}';

? ? }

}

雖然在Unsafe類中存在getUnsafe()方法,但該方法只提供給高級的Bootstrap類加載器使用罢猪,普通用戶調(diào)用將拋出異常近她,所以我們在Demo中使用了反射技術(shù)獲取了Unsafe實(shí)例對象并進(jìn)行相關(guān)操作叉瘩。

public static Unsafe getUnsafe() {

? ? ? Class cc = sun.reflect.Reflection.getCallerClass(2);

? ? ? if (cc.getClassLoader() != null)

? ? ? ? ? throw new SecurityException("Unsafe");

? ? ? return theUnsafe;

? }

數(shù)組操作

//獲取數(shù)組第一個元素的偏移地址

public native int arrayBaseOffset(Class arrayClass);

//數(shù)組中一個元素占據(jù)的內(nèi)存空間,arrayBaseOffset與arrayIndexScale配合使用膳帕,可定位數(shù)組中每個元素在內(nèi)存中的位置

public native int arrayIndexScale(Class arrayClass);

CAS 操作相關(guān)?

CAS是一些CPU直接支持的指令,也就是我們前面分析的無鎖操作薇缅,在Java中無鎖操作CAS基于以下3個方法實(shí)現(xiàn)危彩,在稍后講解Atomic系列內(nèi)部方法是基于下述方法的實(shí)現(xiàn)的。

//第一個參數(shù)o為給定對象泳桦,offset為對象內(nèi)存的偏移量汤徽,通過這個偏移量迅速定位字段并設(shè)置或獲取該字段的值,

//expected表示期望值灸撰,x表示要設(shè)置的值谒府,下面3個方法都通過CAS原子指令執(zhí)行操作。

public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);?

public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);

public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);

這里還需介紹Unsafe類中JDK 1.8新增的幾個方法浮毯,它們的實(shí)現(xiàn)是基于上述的CAS方法完疫,如下:

//1.8新增,給定對象o债蓝,根據(jù)獲取內(nèi)存偏移量指向的字段壳鹤,將其增加delta,

//這是一個CAS操作過程饰迹,直到設(shè)置成功方能退出循環(huán)芳誓,返回舊值

public final int getAndAddInt(Object o, long offset, int delta) {

? ? int v;

? ? do {

? ? ? ? //獲取內(nèi)存中最新值

? ? ? ? v = getIntVolatile(o, offset);

? ? ? //通過CAS操作

? ? } while (!compareAndSwapInt(o, offset, v, v + delta));

? ? return v;

}

//1.8新增余舶,方法作用同上,只不過這里操作的long類型數(shù)據(jù)

public final long getAndAddLong(Object o, long offset, long delta) {

? ? long v;

? ? do {

? ? ? ? v = getLongVolatile(o, offset);

? ? } while (!compareAndSwapLong(o, offset, v, v + delta));

? ? return v;

}

//1.8新增锹淌,給定對象o匿值,根據(jù)獲取內(nèi)存偏移量對于字段,將其設(shè)置為新值newValue葛圃,

//這是一個CAS操作過程千扔,直到設(shè)置成功方能退出循環(huán),返回舊值

public final int getAndSetInt(Object o, long offset, int newValue) {

? ? int v;

? ? do {

? ? ? ? v = getIntVolatile(o, offset);

? ? } while (!compareAndSwapInt(o, offset, v, newValue));

? ? return v;

}

// 1.8新增库正,同上曲楚,操作的是long類型

public final long getAndSetLong(Object o, long offset, long newValue) {

? ? long v;

? ? do {

? ? ? ? v = getLongVolatile(o, offset);

? ? } while (!compareAndSwapLong(o, offset, v, newValue));

? ? return v;

}

//1.8新增,同上褥符,操作的是引用類型數(shù)據(jù)

public final Object getAndSetObject(Object o, long offset, Object newValue) {

? ? Object v;

? ? do {

? ? ? ? v = getObjectVolatile(o, offset);

? ? } while (!compareAndSwapObject(o, offset, v, newValue));

? ? return v;

}

上述的方法我們在稍后的Atomic系列分析中還會見到它們的身影龙誊。

掛起與恢復(fù)?

將一個線程進(jìn)行掛起是通過park方法實(shí)現(xiàn)的,調(diào)用 park后喷楣,線程將一直阻塞直到超時或者中斷等條件出現(xiàn)趟大。unpark可以終止一個掛起的線程,使其恢復(fù)正常铣焊。Java對線程的掛起操作被封裝在 LockSupport類中逊朽,LockSupport類中有各種版本pack方法,其底層實(shí)現(xiàn)最終還是使用Unsafe.park()方法和Unsafe.unpark()方法:

//線程調(diào)用該方法曲伊,線程將一直阻塞直到超時叽讳,或者是中斷條件出現(xiàn)。

public native void park(boolean isAbsolute, long time);

//終止掛起的線程坟募,恢復(fù)正常.java.util.concurrent包中掛起操作都是在LockSupport類實(shí)現(xiàn)的岛蚤,其底層正是使用這兩個方法,

public native void unpark(Object thread);

內(nèi)存屏障

這里主要包括了loadFence懈糯、storeFence涤妒、fullFence等方法,這些方法是在Java 8新引入的赚哗,用于定義內(nèi)存屏障她紫,避免代碼重排序,與Java內(nèi)存模型相關(guān)屿储,感興趣的可以看博主的另一篇博文全面理解Java內(nèi)存模型(JMM)及volatile關(guān)鍵字贿讹,這里就不展開了。

//在該方法之前的所有讀操作扩所,一定在load屏障之前執(zhí)行完成

public native void loadFence();

//在該方法之前的所有寫操作围详,一定在store屏障之前執(zhí)行完成

public native void storeFence();

//在該方法之前的所有讀寫操作,一定在full屏障之前執(zhí)行完成,這個內(nèi)存屏障相當(dāng)于上面兩個的合體功能

public native void fullFence();

其他操作

//獲取持有鎖助赞,已不建議使用

@Deprecated

public native void monitorEnter(Object var1);

//釋放鎖买羞,已不建議使用

@Deprecated

public native void monitorExit(Object var1);

//嘗試獲取鎖,已不建議使用

@Deprecated

public native boolean tryMonitorEnter(Object var1);

//獲取本機(jī)內(nèi)存的頁數(shù)雹食,這個值永遠(yuǎn)都是2的冪次方

?public native int pageSize();

//告訴虛擬機(jī)定義了一個沒有安全檢查的類畜普,默認(rèn)情況下這個類加載器和保護(hù)域來著調(diào)用者類

public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);

//加載一個匿名類

public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);

//判斷是否需要加載一個類

public native boolean shouldBeInitialized(Class c);

//確保類一定被加載

public native void ensureClassInitialized(Class c);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市群叶,隨后出現(xiàn)的幾起案子吃挑,更是在濱河造成了極大的恐慌,老刑警劉巖街立,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舶衬,死亡現(xiàn)場離奇詭異,居然都是意外死亡赎离,警方通過查閱死者的電腦和手機(jī)逛犹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來梁剔,“玉大人虽画,你說我怎么就攤上這事∪俨。” “怎么了码撰?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長个盆。 經(jīng)常有香客問我脖岛,道長,這世上最難降的妖魔是什么砾省? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任鸡岗,我火速辦了婚禮混槐,結(jié)果婚禮上编兄,老公的妹妹穿的比我還像新娘。我一直安慰自己声登,他們只是感情好狠鸳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著悯嗓,像睡著了一般件舵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脯厨,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天铅祸,我揣著相機(jī)與錄音,去河邊找鬼。 笑死临梗,一個胖子當(dāng)著我的面吹牛涡扼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盟庞,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼吃沪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了什猖?” 一聲冷哼從身側(cè)響起票彪,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎不狮,沒想到半個月后降铸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡摇零,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年垮耳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遂黍。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡终佛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出雾家,到底是詐尸還是另有隱情铃彰,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布芯咧,位于F島的核電站牙捉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏敬飒。R本人自食惡果不足惜邪铲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望无拗。 院中可真熱鬧带到,春花似錦、人聲如沸英染。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽四康。三九已至搪搏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闪金,已是汗流浹背疯溺。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人囱嫩。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓嗅辣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親挠说。 傳聞我的和親對象是個殘疾皇子澡谭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355