深入解析 volatile 、CAS 的實現(xiàn)原理

在分析說明 volatile 和 CAS 的實現(xiàn)原理前,我們需要先了解一些預(yù)備知識纳寂,這將是對 volatile 和 CAS 有深入理解的基石。

預(yù)備知識

緩存

現(xiàn)代處理器為了提高訪問數(shù)據(jù)的效率泻拦,在每個CPU核心上都會有多級容量小毙芜,速度快的緩存(分別稱之為L1 cache,L2 cache争拐,多核心共享L3 cache等)腋粥,用于緩存常用的數(shù)據(jù)。

緩存系統(tǒng)中是以緩存行(cache line)為單位存儲的架曹。緩存行是 2 的整數(shù)冪個連續(xù)字節(jié)隘冲,一般為 32-256 個字節(jié)。最常見的緩存行大小是 64個字節(jié)音瓷。

因此當(dāng)CPU在執(zhí)行一條讀內(nèi)存指令時对嚼,它是會將內(nèi)存地址所在的緩存行大小的內(nèi)容都加載進緩存中的。也就是說绳慎,一次加載一整個緩存行纵竖。

但寫操作就比較復(fù)雜了。寫操作有兩種基本的模式:
① 直寫(write-through)
直寫是透過本級緩存杏愤,直接把數(shù)據(jù)寫到下一級緩存(或直接到內(nèi)存)中靡砌,如果對應(yīng)的段被緩存了,我們同時更新緩存中的內(nèi)容(甚至直接丟棄)珊楼。
所以通殃,直寫時緩存行永遠和它對應(yīng)的內(nèi)存內(nèi)容匹配。
② 回寫(write-back)
緩存不會立即把寫操作傳遞到下一級厕宗,而是僅修改本級緩存中的數(shù)據(jù)画舌,并且把對應(yīng)的緩存段標(biāo)記為“臟”段。臟段會觸發(fā)回寫已慢,也就是把里面的內(nèi)容寫到對應(yīng)的內(nèi)存或下一級緩存中曲聂。回寫后佑惠,臟段又變“干凈”了朋腋。當(dāng)一個臟段被丟棄的時候齐疙,總是先要進行一次回寫。
也就是說旭咽,回寫模式中要么緩存段的內(nèi)容和內(nèi)存一致(如果緩存段是干凈的話)贞奋;要么緩存段中的內(nèi)容最終要回寫到內(nèi)存中(對于臟緩存段來說)。

緩存一致性協(xié)議

在多核處理器系統(tǒng)中穷绵,每個處理器核心都有它們自己的一級緩存轿塔、二級緩存等。這樣一來當(dāng)多個處理器核心在對共享的數(shù)據(jù)進行寫操作時仲墨,就需要保證該共享數(shù)據(jù)在所有處理器核心中的可見性/一致性催训。
『 窺探技術(shù) + MESI協(xié)議 』的出現(xiàn),就是為了解決多核處理器時代宗收,緩存不一致的問題的漫拭。

窺探技術(shù)

“窺探”背后的基本思想是,所有內(nèi)存?zhèn)鬏敹及l(fā)生在一條共享的總線上混稽,而所有的處理器都能看到這條總線:緩存本身是獨立的采驻,但是內(nèi)存是共享資源,所有的內(nèi)存訪問都要經(jīng)過仲裁(arbitrate):同一個指令周期中匈勋,只有一個緩存可以讀寫內(nèi)存礼旅。窺探技術(shù)的思想是,緩存不僅僅在做內(nèi)存?zhèn)鬏數(shù)臅r候才和總線打交道洽洁,而是不停地在窺探總線上發(fā)生的數(shù)據(jù)交換痘系,跟蹤其他緩存在做什么。所以當(dāng)一個緩存代表它所屬的處理器去讀寫內(nèi)存時饿自,其他處理器都會得到通知汰翠,它們以此來使自己的緩存保持同步。只要某個處理器一寫內(nèi)存昭雌,其他處理器馬上就知道這塊內(nèi)存在它們自己的緩存中對應(yīng)的緩存行已經(jīng)失效复唤。

MESI協(xié)議

我們知道,緩存系統(tǒng)操作的最小單位就是緩存行烛卧,而MESI是緩存行四種狀態(tài)的首字母縮寫佛纫,任何多核系統(tǒng)中的緩存行都處于這四種狀態(tài)之一。
失效(Invalid)緩存行:該處理器緩存中無該緩存行总放,或緩存中的緩存行已經(jīng)失效了呈宇。
共享(Shared)緩存行:緩存行的內(nèi)容是同主內(nèi)存內(nèi)容保持一致的一份拷貝,在這種狀態(tài)下的緩存行只能被讀取局雄,不能被寫入甥啄。多組緩存可以同時擁有針對同一內(nèi)存地址的共享緩存行。
獨占(Exclusive)緩存行:和S狀態(tài)一樣哎榴,也是和主內(nèi)存內(nèi)容保持一致的一份拷貝型豁。區(qū)別在于,如果一個處理器持有了某個E狀態(tài)的緩存行尚蝌,那其他處理器就不能同時持該內(nèi)容的緩存行迎变,所以叫“獨占”。這意味著飘言,如果其他處理器原本也持有同一緩存行衣形,那么它會馬上變成“失效”狀態(tài)(I狀態(tài))。
已修改(Modified)緩存行:屬于臟段姿鸿,該緩存行已經(jīng)被所屬的處理器修改了谆吴。如果一個緩存行處于已修改狀態(tài),那么它在其他處理器緩存中的拷貝馬上會變成失效狀態(tài)苛预,這個規(guī)律和E狀態(tài)一樣句狼。此外,已修改緩存行如果被丟棄或標(biāo)記為失效(即热某,從M狀態(tài) ——> I狀態(tài))腻菇,那么先要把它的內(nèi)容回寫到內(nèi)存中 ———— 這和回寫模式下常規(guī)的臟段處理方式一樣。

只有當(dāng)緩存行處于E或M狀態(tài)時昔馋,處理器才能去寫它筹吐,也就是說只有這兩種狀態(tài)下,處理器是獨占這個緩存行的秘遏。當(dāng)處理器想寫某個緩存段時丘薛,如果它沒有獨占權(quán),它必須先發(fā)送一條“我要獨占權(quán)”的請求給總線邦危,這會通知其他處理器洋侨,把它們擁有的同一緩存行的拷貝失效(I狀態(tài)),如果它們有的話倦蚪。只有在獲得獨占權(quán)后凰兑,處理器才能開始修改數(shù)據(jù)。并且此時审丘,這個處理器知道吏够,這個緩存行只有一份拷貝,在我自己的緩存里滩报,所以不會有任何沖突锅知。反之,如果有其他處理器想讀取這個緩存行(我們馬上能知道脓钾,因為我們一直在窺探總線)售睹,獨占或已修改的緩存行必須先回到“共享”狀態(tài)。如果是已修改的緩存段可训,那么還要先把內(nèi)容回寫到內(nèi)存中昌妹。

原子操作

原子(atomic)本意是“不能被進一步分割的最小粒子”捶枢,而原子操作(atomic operation)意為“不可被中斷的一個或一系列操作”。

好了飞崖,有了上面這些淺顯理論基礎(chǔ)之后烂叔,讓我們開始本文主題的探討。

volatile的內(nèi)存語義

volatile變量自身具有下列特性:

可見性/一致性:對一個 volatile 變量的讀固歪,總是能看到(任意線程)對這個 volatile 變量最后的寫入蒜鸡。
原子性:對任意單個 volatile 變量的讀/寫具有原子性,但類似于 volatile++這種復(fù)合操作不具有原子性牢裳。

volatile 寫-讀建立的 happens before 關(guān)系

happens-before 規(guī)則中有這么一條:
volatile變量規(guī)則:對一個volatile域的寫逢防,happens-before于任意后續(xù)對這個volatile域的讀。


happens-before的這個規(guī)則會保證volatile寫-讀具有如下的內(nèi)存語義:

  • volatile寫的內(nèi)存語義:
    當(dāng)寫一個 volatile 變量時蒲讯,JMM 會把該線程對應(yīng)的本地內(nèi)存中的共享變量值刷新到主內(nèi)存忘朝。
  • volatile讀的內(nèi)存語義:
    當(dāng)讀一個 volatile 變量時,JMM 會把該線程對應(yīng)的本地內(nèi)存置為無效判帮。線程接下來將從主內(nèi)存中讀取共享變量辜伟。
volatile內(nèi)存語義的實現(xiàn)原理

為了實現(xiàn) volatile 的內(nèi)存語義,編譯器在生成字節(jié)碼時脊另,會在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序导狡。因為內(nèi)存屏障是一組處理器指令,它并不由JVM直接暴露偎痛,因此JVM會根據(jù)不同的操作系統(tǒng)插入不同的指令以達成我們所要內(nèi)存屏障效果旱捧。

為了保證內(nèi)存可見性,java編譯器在生成指令序列的適當(dāng)位置會插入內(nèi)存屏障指令來禁止特定類型的處理器重排序踩麦。JMM把內(nèi)存屏障指令分為下列四類:

下面是基于保守策略的JMM內(nèi)存屏障插入策略:
在每個volatile寫操作的前面插入一個StoreStore屏障枚赡。
在每個volatile寫操作的后面插入一個StoreLoad屏障。
在每個volatile讀操作的后面插入一個LoadLoad屏障谓谦。
在每個volatile讀操作的后面插入一個LoadStore屏障贫橙。

上述內(nèi)存屏障插入策略非常保守,但它可以保證在任意處理器平臺反粥,任意的程序中都能得到正確的volatile內(nèi)存語義卢肃。由于不同的處理器有不同“松緊度”的處理器內(nèi)存模型,內(nèi)存屏障的插入還可以根據(jù)具體的處理器內(nèi)存模型繼續(xù)優(yōu)化才顿。

在X86處理器上實現(xiàn)volatile的內(nèi)存語義

在X86中莫湘,JMM僅需在volatile寫后面插入一個StoreLoad屏障即可正確實現(xiàn)volatile寫-讀的內(nèi)存語義。這是因為X86不會對讀-讀郑气,讀-寫和寫-寫操作做重排序幅垮,因此在X86處理器中會省略掉這三種操作類型對應(yīng)的內(nèi)存屏障。

這里需要再次強調(diào)說明的是尾组,內(nèi)存屏障是一組處理器指令忙芒,而上面的四種內(nèi)存屏障(StoreStore示弓、StoreLoad、LoadLoad呵萨、LoadStore)這是JMM內(nèi)存屏障的一種分類奏属,在不同的處理器中,會轉(zhuǎn)換成相應(yīng)處理器對應(yīng)的該內(nèi)存屏障類型的指令甘桑。比如,同樣是StoreLoad屏障歹叮,在SPARC中的指令是membar跑杭、在Xeon中的指令是mfence、在Itanium中的指令是mf咆耿。

在X86處理器上執(zhí)行volatile寫操作時會插入一個帶有l(wèi)ock前綴(匯編指令)來實現(xiàn)volatile的內(nèi)存語義的德谅。如,『0x01a3de24: lock addl $0x0,(%esp);』
至于lock前綴是如何保證volatile的內(nèi)存語義(包括volatile自身的特定以及volatile 寫-讀建立的 happens before 的內(nèi)存語義)萨螺,將在下面介紹CAS時對lock前綴進行一個詳細的說明窄做。

CAS ———— compareAndSet

/**
  * Atomically sets the field of the given object managed by this updater
  * to the given updated value if the current value {@code ==} the
  * expected value. This method is guaranteed to be atomic with respect to
  * other calls to {@code compareAndSet} and {@code set}, but not
  * necessarily with respect to other changes in the field.
  *
  * @param obj An object whose field to conditionally set
  * @param expect the expected value
  * @param update the new value
  * @return {@code true} if successful
  * @throws ClassCastException if {@code obj} is not an instance
  * of the class possessing the field established in the constructor
*/
public abstract boolean compareAndSet(T obj, int expect, int update);

以原子的方式更新這個更新器所管理的對象(obj)的成員變量,并且將這個成員變量更新為給定的更新后的值(update)如果當(dāng)前值等于期望值(expect)時慰技。
當(dāng)存在其他使用‘compareAndSet’或者’set’的情況下椭盏,這個方法可以確保是原子的,但如果你用其他的方式去改變這個成員變量時(如吻商,使用直接賦值的方式 field=newField)掏颊,那么它是不會遵循這個原子性的。同時艾帐,該操作具有volatile 讀和寫的內(nèi)存語義乌叶。

前面我們已經(jīng)介紹了原子操作的概念,所以這里CAS涉及的兩步:a) 只有field的值為expect時柒爸;b) 將field的值修改為update的值准浴;將視為一個原子操作。

處理器如何實現(xiàn)原子操作

首先處理器會自動保證基本的內(nèi)存操作的原子性捎稚。同時乐横,處理器提供總線鎖定緩存鎖定兩個機制來保證復(fù)雜內(nèi)存操作的原子性。
① 使用總線鎖保證原子性
所謂總線鎖就是使用處理器提供的一個LOCK#信號今野,當(dāng)一個處理器在總線上輸出此信號時晰奖,其他處理器的請求將被阻塞住,那么該處理器可以獨占使用共享內(nèi)存。

② 使用緩存鎖保證原子性
在同一時刻我們只需保證對某個內(nèi)存地址的操作是原子性即可腥泥,但總線鎖定把CPU和內(nèi)存之間通信鎖住了匾南,這使得鎖定期間,其他處理器不能操作其他內(nèi)存地址的數(shù)據(jù)蛔外,所以總線鎖定的開銷比較大蛆楞,最近的處理器在某些場合下使用緩存鎖定代替總線鎖定來進行優(yōu)化溯乒。
在奔騰6和最近的處理器中可以使用“緩存鎖定”的方式來實現(xiàn)復(fù)雜的原子性。所謂“緩存鎖定”就是如果緩存在處理器緩存行中內(nèi)存區(qū)域在LOCK操作期間被鎖定豹爹,當(dāng)它執(zhí)行鎖操作回寫內(nèi)存時钝鸽,處理器不在總線上聲言LOCK#信號,而是修改內(nèi)部的內(nèi)存地址溪掀,并允許它的緩存一致性機制來保證操作的原子性齿拂,因為緩存一致性機制會阻止同時修改被兩個以上處理器緩存的內(nèi)存區(qū)域數(shù)據(jù),當(dāng)其他處理器回寫已被鎖定的緩存行的數(shù)據(jù)時會起緩存行無效孩等。

在JDK 8艾君,Linux操作系統(tǒng),X86處理器環(huán)境下肄方,CAS的源碼如下:
// Adding a lock prefix to an instruction on MP machine
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  int mp = os::is_MP();
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
}

程序會根據(jù)當(dāng)前處理器的類型來決定是否為cmpxchg指令添加lock前綴冰垄。如果程序是在多處理器上運行,就為cmpxchg指令加上lock前綴(lock cmpxchg)权她。反之虹茶,如果程序是在單處理器上運行,就省略lock前綴(單處理器自身會維護單處理器內(nèi)的順序一致性隅要,不需要lock前綴提供的內(nèi)存屏障效果)蝴罪。

cmpxchgl的詳細執(zhí)行過程:
首先,輸入是"r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)步清,表示compare_value存入eax寄存器洲炊,而exchange_value、dest尼啡、mp的值存入任意的通用寄存器暂衡。嵌入式匯編規(guī)定把輸出和輸入寄存器按統(tǒng)一順序編號,順序是從輸出寄存器序列從左到右從上到下以“%0”開始崖瞭,分別記為%0狂巢、%1···%9。也就是說书聚,輸出的eax是%0唧领,輸入的exchange_value、compare_value雌续、dest斩个、mp分別是%1、%2驯杜、%3受啥、%4。
因此,cmpxchgl %1,(%3)實際上表示cmpxchgl exchange_value,(dest)滚局,此處(dest)表示dest地址所存的值居暖。需要注意的是cmpxchgl有個隱含操作數(shù)eax,其實際過程是先比較eax的值(也就是compare_value)和dest地址所存的值是否相等藤肢,如果相等則把exchange_value的值寫入dest指向的地址太闺。如果不相等則把dest地址所存的值存入eax中。
輸出是"=a" (exchange_value)嘁圈,表示把eax中存的值寫入exchange_value變量中省骂。
Atomic::cmpxchg這個函數(shù)最終返回值是exchange_value,也就是說最住,如果cmpxchgl執(zhí)行時compare_value和dest指針指向內(nèi)存值相等則會使得dest指針指向內(nèi)存值變成exchange_value钞澳,最終eax存的compare_value賦值給了exchange_value變量,即函數(shù)最終返回的值是原先的compare_value温学。此時Unsafe_CompareAndSwapInt的返回值(jint)(Atomic::cmpxchg(x, addr, e)) == e就是true略贮,表明CAS成功甚疟。如果cmpxchgl執(zhí)行時compare_value和(dest)不等則會把當(dāng)前dest指針指向內(nèi)存的值寫入eax仗岖,最終輸出時賦值給exchange_value變量作為返回值,導(dǎo)致(jint)(Atomic::cmpxchg(x, addr, e)) == e得到false览妖,表明CAS失敗轧拄。

lock前綴

lock前綴的指令的說明:
保證指令的執(zhí)行的原子性
帶有l(wèi)ock前綴的指令在執(zhí)行期間會鎖住總線,使得其他處理器暫時無法通過總線訪問內(nèi)存讽膏。很顯然檩电,這會帶來昂貴的開銷。從Pentium 4府树,Intel Xeon及P6處理器開始俐末,intel在原有總線鎖的基礎(chǔ)上做了一個很有意義的優(yōu)化:如果要訪問的內(nèi)存區(qū)域(area of memory)在lock前綴指令執(zhí)行期間已經(jīng)在處理器內(nèi)部的緩存中被鎖定(即包含該內(nèi)存區(qū)域的緩存行當(dāng)前處于獨占或以修改狀態(tài)),并且該內(nèi)存區(qū)域被完全包含在單個緩存行(cache line)中奄侠,那么處理器將直接執(zhí)行該指令卓箫。由于在指令執(zhí)行期間該緩存行會一直被鎖定,其它處理器無法讀/寫該指令要訪問的內(nèi)存區(qū)域垄潮,因此能保證指令執(zhí)行的原子性烹卒。
禁止該指令與之前和之后的讀和寫指令重排序
在AI-32架構(gòu)軟件開發(fā)者手冊第8中內(nèi)存排序中,有說明LOCK前綴會禁止指令與之前和之后的讀和寫指令重排序弯洗。這相當(dāng)于JMM中定義的StoreLoad內(nèi)存屏障的效果旅急。也正是因為這個內(nèi)存屏障的效果,會使得線程把其寫緩沖區(qū)中的所有數(shù)據(jù)刷新到內(nèi)存中牡整。注意藐吮,這里不是單單被修改的數(shù)據(jù)會被回寫到主內(nèi)存,而是寫緩存中所有的數(shù)據(jù)都回寫到主內(nèi)存。
而將寫緩沖區(qū)的數(shù)據(jù)回寫到內(nèi)存時炎码,就會通過緩存一致性協(xié)議(如盟迟,MESI協(xié)議)和窺探技術(shù)來保證寫入的數(shù)據(jù)被其他處理器的緩存可見。
而這就相當(dāng)于實現(xiàn)了volatile的內(nèi)存語義潦闲。是的攒菠,上面我們?yōu)檎f明的lock前綴是如何實現(xiàn)volatile的內(nèi)存語義就是這么保證的。

所以歉闰,我們可以知道CAS的指令的原子性辖众,以及內(nèi)存語義就是通過lock前綴指令來完成的。

解惑

怎么說了和敬,由于筆者對操作系統(tǒng)以及C++的一無所知凹炸,以至于在看《Java 并發(fā)編程的藝術(shù)》以及相關(guān)的一些文章,要么會漏掉一些細節(jié)昼弟,要么就是獨立的概念看著好像都懂啤它,但串起來就蒙圈了。舱痘。变骡。 這里,暫且記錄一些筆者非常粗淺的認知芭逝。

① 處理器上有一套完整的協(xié)議塌碌,來保證Cache一致性。比較經(jīng)典的Cache一致性協(xié)議當(dāng)屬MESI協(xié)議旬盯。也就是說台妆,MESI協(xié)議是處理器自身會遵循的一個緩沖一致性協(xié)議。

② lock前綴 本身帶有全屏障的效應(yīng)胖翰,所以不會在lock前綴指令后再插入內(nèi)存屏障指令

③ Q:既然處理器本身已經(jīng)維護了緩存的一致性接剩,那為什么還會出現(xiàn)多線程操作兩個共享變量時出現(xiàn)“預(yù)期之外”的結(jié)果了?(這里共享變量不帶有volatile萨咳,并且操作不加鎖以及序列化流程控制)
比如:
簡單說懊缺,初始時x=0, y=0
p0處理器做
x = 1
r1 = x
r2 = y
p1處理器做
y = 1
r3 = y
r4 = x
最后結(jié)果可以出現(xiàn)r2 = 0且r4 = 0的情況。

A:是這樣的某弦,線程的本地內(nèi)存是JMM的一個抽象概念桐汤,并不真實存在。它涵蓋了緩存靶壮,寫緩沖區(qū)怔毛,寄存器以及其他的硬件和編譯器優(yōu)化。同時腾降,現(xiàn)代的處理器使用寫緩沖區(qū)來臨時保存向內(nèi)存寫入的數(shù)據(jù)拣度。寫緩沖區(qū)可以保證指令流水線持續(xù)運行,它可以避免由于處理器停頓下來等待向內(nèi)存寫入數(shù)據(jù)而產(chǎn)生的延遲。
所以說抗果,如果在沒加鎖的話筋帖,p0處理器可能在將[_x]=1寫到了寫緩沖區(qū)后就接著執(zhí)行后面的指令了,這就使得『[_x]=1』操作并未真的寫到主內(nèi)存中冤馏,那么p1處理器就無法看到真實的值日麸。但如果使用鎖的話,p0處理器會確贝猓『[_x]=1』操作是已經(jīng)將數(shù)據(jù)寫到主內(nèi)存后才往下執(zhí)行的代箭,同時p1處理器也是保證『[_y]=1』操作是將數(shù)據(jù)寫到主內(nèi)存了才會往下執(zhí)行下面的指令。這樣一來p0就能看到正確的y值涕刚,p1就能看到正確的x值了嗡综。

后記

筆者是個計算機基礎(chǔ)方面的小白,如果文章有錯不吝指教 :)

參考

《Java 并發(fā)編程的藝術(shù)》
http://www.infoq.com/cn/articles/cache-coherency-primer
http://blog.csdn.net/prstaxy/article/details/51802220
https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf
https://www.zhihu.com/question/26848513
https://stackoverflow.com/questions/4232660/which-is-a-better-write-barrier-on-x86-lockaddl-or-xchgl

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杜漠,一起剝皮案震驚了整個濱河市极景,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌驾茴,老刑警劉巖盼樟,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異沟涨,居然都是意外死亡恤批,警方通過查閱死者的電腦和手機异吻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門裹赴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诀浪,你說我怎么就攤上這事棋返。” “怎么了雷猪?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵睛竣,是天一觀的道長。 經(jīng)常有香客問我求摇,道長射沟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任与境,我火速辦了婚禮验夯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘摔刁。我一直安慰自己挥转,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绑谣,像睡著了一般党窜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上借宵,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天幌衣,我揣著相機與錄音,去河邊找鬼壤玫。 笑死泼掠,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的垦细。 我是一名探鬼主播择镇,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼括改!你這毒婦竟也來了腻豌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤嘱能,失蹤者是張志新(化名)和其女友劉穎吝梅,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惹骂,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡苏携,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了对粪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片右冻。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖著拭,靈堂內(nèi)的尸體忽然破棺而出纱扭,到底是詐尸還是另有隱情,我是刑警寧澤儡遮,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布乳蛾,位于F島的核電站,受9級特大地震影響鄙币,放射性物質(zhì)發(fā)生泄漏肃叶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一十嘿、第九天 我趴在偏房一處隱蔽的房頂上張望因惭。 院中可真熱鬧,春花似錦详幽、人聲如沸筛欢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽版姑。三九已至柱搜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間剥险,已是汗流浹背聪蘸。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留表制,地道東北人健爬。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像么介,于是被迫代替她去往敵國和親娜遵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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