深入理解Java內(nèi)存模型

CPU與緩存一致性問(wèn)題

我們都應(yīng)該知道線程是 CPU 調(diào)度的最小單位瀑凝,線程中的字節(jié)碼指令最終都是在 CPU 中執(zhí)行的。CPU在執(zhí)行的時(shí)候亩码,免不了要和各種數(shù)據(jù)打交道瞬场,而 Java 中所有數(shù)據(jù)都是存放在主內(nèi)存(RAM)當(dāng)中的,這一過(guò)程可以參考下圖:


但是隨著CPU技術(shù)的發(fā)展句喜,CPU的執(zhí)行速度越來(lái)越快预愤。而由于內(nèi)存的技術(shù)并沒(méi)有太大的變化,所以從內(nèi)存中讀取和寫(xiě)入數(shù)據(jù)的過(guò)程和CPU的執(zhí)行速度比起來(lái)差距就會(huì)越來(lái)越大,這就導(dǎo)致CPU每次操作內(nèi)存都要耗費(fèi)很多等待時(shí)間咳胃。

為了提升CPU操作內(nèi)存的性能植康,在 CPU 中添加了高速緩存 (cache)來(lái)作為緩沖。如下圖:


在執(zhí)行任務(wù)時(shí)展懈,CPU 會(huì)先將運(yùn)算所需要使用到的數(shù)據(jù)復(fù)制到高速緩存中销睁,讓運(yùn)算能夠快速進(jìn)行,當(dāng)運(yùn)算完成之后存崖,再將高速緩存中的結(jié)果刷回(flush back)主內(nèi)存冻记,這樣 CPU 就不用等待主內(nèi)存的讀寫(xiě)操作了。

高速緩存提升了CPU 操作緩存的性能来惧,但也帶來(lái)了緩存一致性問(wèn)題:每個(gè)CPU 都有自己的高速緩存冗栗,同時(shí)又共同操作同一塊主內(nèi)存,當(dāng)多個(gè)處理器同時(shí)操作主內(nèi)存時(shí)供搀,可能導(dǎo)致數(shù)據(jù)不一致隅居。如下圖:


CPU1 和 CPU2 都將需要用到的數(shù)據(jù)從主內(nèi)存拷貝到自己的高速緩存中,當(dāng) CPU1 更新與 CPU2 的共享數(shù)據(jù)到主內(nèi)存時(shí)葛虐,CPU2 中高速緩存的數(shù)據(jù)并沒(méi)有更新军浆,這就會(huì)造成 CPU1 和 CPU2 對(duì)主內(nèi)存中同一個(gè)數(shù)據(jù)緩存的內(nèi)容不一致。

處理器優(yōu)化和指令重排

上面提到在在CPU和主存之間增加緩存挡闰,在多線程場(chǎng)景下會(huì)存在緩存一致性問(wèn)題。除了這種情況,還有一種硬件問(wèn)題也比較重要摄悯。那就是為了使處理器內(nèi)部的運(yùn)算單元能夠盡量的被充分利用赞季,處理器可能會(huì)對(duì)輸入代碼進(jìn)行亂序執(zhí)行處理。這就是處理器優(yōu)化奢驯。

除了現(xiàn)在很多流行的處理器會(huì)對(duì)代碼進(jìn)行優(yōu)化亂序處理申钩,很多編程語(yǔ)言的編譯器也會(huì)有類(lèi)似的優(yōu)化,比如Java虛擬機(jī)的即時(shí)編譯器(JIT)也會(huì)做指令重排瘪阁。

指令重排案例:
代碼如下:

        a = 1;
        b  = 2;
        a = a + 1;

編譯之后的字節(jié)碼指令如下:

0:iconst_1    // 將常量1壓入操作數(shù)棧
1:istore_1    // 將棧頂元素保存到局部變量表下標(biāo)1處
2:iconst_2    // 將常量2壓入操作數(shù)棧
3:istore_2    // 將棧頂元素保存到局部變量表下標(biāo)2處
4:iload_1     // 將局部變量表下標(biāo)1處元素壓入操作數(shù)棧
5:iconst_1    // 將常量1壓入操作數(shù)棧
6:iadd        // 將棧頂?shù)膬蓚€(gè)元素相加撒遣,并將結(jié)果壓入操作數(shù)棧
7:istore_1    // 將棧頂元素保存到局部變量表下標(biāo)1處

可以看出在上述指令中,有兩處指令表達(dá)的是同樣的語(yǔ)義:istore_1管跺,并且指令 7 并不依賴指令 2 和指令 3义黎。這種情況下,CPU 會(huì)對(duì)指令的順序做優(yōu)化豁跑,如下圖:


從 Java 語(yǔ)言的角度看這層優(yōu)化就是:


也就是說(shuō)在 CPU 層面廉涕,有時(shí)候代碼并不會(huì)嚴(yán)格按照 Java 文件中的順序去執(zhí)行⊥模可想而知狐蜕,如果任由處理器優(yōu)化和編譯器對(duì)指令重排的話,就可能導(dǎo)致各種各樣的問(wèn)題卸夕。

什么是Java內(nèi)存模型

Java內(nèi)存模型(Java Memory Model)层释,是一套共享內(nèi)存系統(tǒng)中多線程讀寫(xiě)操作行為的規(guī)范,這套規(guī)范屏蔽了底層各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異快集,解決了 CPU 多級(jí)緩存贡羔、CPU 優(yōu)化、指令重排等導(dǎo)致的內(nèi)存訪問(wèn)問(wèn)題碍讨,從而保證 Java 程序(尤其是多線程程序)在各種平臺(tái)下對(duì)內(nèi)存的訪問(wèn)效果一致治力。

在 Java 內(nèi)存模型中,我們統(tǒng)一用工作內(nèi)存(working memory)來(lái)當(dāng)作 CPU 中寄存器或高速緩存的抽象勃黍。線程之間的共享變量存儲(chǔ)在主內(nèi)存(main memory)中宵统,每個(gè)線程都有一個(gè)私有工作內(nèi)存(類(lèi)比 CPU 中的寄存器或者高速緩存),本地工作內(nèi)存中存儲(chǔ)了該線程讀/寫(xiě)共享變量的副本覆获。

在這套規(guī)范中马澈,有一個(gè)非常重要的規(guī)則——happens-before

happens-before 先行發(fā)生原則

happens-before 用于描述兩個(gè)操作的內(nèi)存可見(jiàn)性弄息,通過(guò)保證可見(jiàn)性的機(jī)制可以讓?xiě)?yīng)用程序免于數(shù)據(jù)競(jìng)爭(zhēng)干擾痊班。它的定義如下:

如果一個(gè)操作 A happens-before 另一個(gè)操作 B,那么操作 A 的執(zhí)行結(jié)果將對(duì)操作 B 可見(jiàn)摹量。

上述定義我們也可以反過(guò)來(lái)理解:

如果操作 A 的結(jié)果需要對(duì)另外一個(gè)操作 B 可見(jiàn)涤伐,那么操作 A 必須 happens-before 操作 B馒胆。

用以下代碼來(lái)舉例:

    private int value = 0;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

假設(shè) setValue 就是操作 A,getValue 就是操作 B凝果。如果我們先后在兩個(gè)線程中調(diào)用 A 和 B祝迂,那最后在 B 操作中返回的 value 值是多少呢?有以下兩種情況:

1. 如果 A happens-before B 不成立

也就是說(shuō)當(dāng)線程調(diào)用操作 B(getValue)時(shí)器净,即使操作 A(setValue)已經(jīng)在其他線程中被調(diào)用過(guò)型雳,并且 value 也被成功設(shè)置為 1,但這個(gè)修改對(duì)于操作 B(getValue)仍然是不可見(jiàn)的山害,value 值有可能返回 0纠俭,也有可能返回 1。

2. 如果 A happens-before B 成立

根據(jù) happens-before 的定義浪慌,先行發(fā)生動(dòng)作的結(jié)果冤荆,對(duì)后續(xù)發(fā)生動(dòng)作是可見(jiàn)的。也就是說(shuō)如果我們先在一個(gè)線程中調(diào)用了操作 A(setValue)方法眷射,那么這個(gè)修改后的結(jié)果對(duì)后續(xù)的操作 B(getValue)始終可見(jiàn)匙赞。因此如果先調(diào)用 setValue 將 value 賦值為 1 后,后續(xù)在其他線程中調(diào)用 getValue 的值一定是 1妖碉。

那么在Java中如何讓兩個(gè)操作符合 happens-before 原則呢涌庭? JMM 中定義了以下幾種情況是自動(dòng)符合 happens-before 規(guī)則的:

  • 程序次序規(guī)則
    在單線程內(nèi)部,如果一段代碼的字節(jié)碼順序也隱式符合 happens-before 原則欧宜,那么邏輯順序靠前的字節(jié)碼執(zhí)行結(jié)果一定是對(duì)后續(xù)邏輯字節(jié)碼可見(jiàn)坐榆,只是后續(xù)邏輯中不一定用到而已。

  • 鎖定規(guī)則
    無(wú)論是在單線程環(huán)境還是多線程環(huán)境冗茸,一個(gè)鎖如果處于被鎖定狀態(tài)席镀,那么必須先執(zhí)行 unlock 操作后才能進(jìn)行 lock 操作。

  • volatile變量規(guī)則
    volatile 保證了線程可見(jiàn)性夏漱。通俗講就是如果一個(gè)線程先寫(xiě)了一個(gè) volatile 變量豪诲,然后另外一個(gè)線程去讀這個(gè)變量,那么這個(gè)寫(xiě)操作一定是 happens-before 讀操作的挂绰。

  • 線程啟動(dòng)規(guī)則
    假定線程 A 在執(zhí)行過(guò)程中屎篱,通過(guò)執(zhí)行 ThreadB.start() 來(lái)啟動(dòng)線程 B,那么線程 A 對(duì)共享變量的修改在線程 B 開(kāi)始執(zhí)行后確保對(duì)線程 B 可見(jiàn)葵蒂。

  • 線程中斷規(guī)則
    對(duì)線程interrupt()方法的調(diào)用 happens-before 被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生交播。

  • 線程終結(jié)規(guī)則
    線程中所有的操作都 happens-before 線程的終止檢測(cè),我們可以通過(guò)Thread.join()方法結(jié)束践付、Thread.isAlive()的返回值手段檢測(cè)到線程已經(jīng)終止執(zhí)行秦士。

  • 對(duì)象終結(jié)規(guī)則
    一個(gè)對(duì)象的初始化完成 happens-before 他的finalize()方法的開(kāi)始

Java 內(nèi)存模型應(yīng)用

上面介紹的 happens-before 原則非常重要,它是判斷數(shù)據(jù)是否存在競(jìng)爭(zhēng)永高、線程是否安全的主要依據(jù)隧土,根據(jù)這個(gè)原則提针,我們能夠解決在并發(fā)環(huán)境下操作之間是否可能存在沖突的所有問(wèn)題。在此基礎(chǔ)上次洼,我們可以通過(guò) Java 提供的一系列關(guān)鍵字关贵,將我們自己實(shí)現(xiàn)的多線程操作“happens-before 化”。

比如我還是用上面的 setValue 和 getValue 舉例卖毁,本來(lái)這兩個(gè)操作是不符合 happens-before 原則的,但是我們可以通過(guò)以下兩種方式落萎,使它們符合 happens-before 原則亥啦。

使用 volatile 修飾 value,代碼如下:

    private volatile int value = 0;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

使用synchronized關(guān)鍵字修飾方法练链,代碼如下:

    private int value = 0;

    synchronized public int getValue() {
        return value;
    }

    synchronized public void setValue(int value) {
        this.value = value;
    }

同步機(jī)制

從修飾類(lèi)型來(lái)看翔脱,同步機(jī)制可以分為3種:

  • 修飾字段的同步機(jī)制
  • 修飾方法的同步機(jī)制
  • 修飾代碼塊

關(guān)于修飾字段的同步機(jī)制:

  • volatile

關(guān)于修飾方法和代碼塊的同步機(jī)制:

  • synchronized

volatile

volatile的中文意思是不穩(wěn)定的,易變的媒鼓,用volatile修飾變量是為了保證變量的可見(jiàn)性届吁。

volatile作用

  • 保證了不同線程對(duì)該volatile修飾的變量操作的內(nèi)存可見(jiàn)性;
  • 禁止進(jìn)行指令重排序

保證可見(jiàn)性

保證了不同線程對(duì)該變量操作的內(nèi)存可見(jiàn)性。

這里保證可見(jiàn)性是不等同于volatile變量并發(fā)操作的安全性绿鸣,保證可見(jiàn)性具體一點(diǎn)解釋:
線程寫(xiě)volatile變量的過(guò)程:

  1. 改變線程工作內(nèi)存中volatile變量副本的值
  2. 將改變后的副本的值從工作內(nèi)存刷新到主內(nèi)存

線程讀volatile變量的過(guò)程:

  1. 從主內(nèi)存中讀取volatile變量的最新值到線程的工作內(nèi)存中
  2. 從工作內(nèi)存中讀取volatile變量的副本

但是如果多個(gè)線程同時(shí)把更新后的變量值同時(shí)刷新回主內(nèi)存疚沐,可能導(dǎo)致得到的值不是預(yù)期結(jié)果。也就是說(shuō)潮模,=volatile的兩點(diǎn)內(nèi)存語(yǔ)義能保證可見(jiàn)性和有序性亮蛔,但是不能保證原子性。

舉個(gè)例子:
定義volatile int count = 0擎厢,2個(gè)線程同時(shí)執(zhí)行count++操作究流,每個(gè)線程都執(zhí)行500次,最終結(jié)果小于1000动遭,原因是每個(gè)線程執(zhí)行count++需要以下3個(gè)步驟:

  • 步驟1 線程從主內(nèi)存讀取最新的count的值
  • 步驟2 執(zhí)行引擎把count值加1芬探,并賦值給線程工作內(nèi)存
  • 步驟3 線程工作內(nèi)存把count值保存到主內(nèi)存

有可能某一時(shí)刻2個(gè)線程在步驟1讀取到的值都是100,執(zhí)行完步驟2得到的值都是101厘惦,最后刷新了2次101保存到主內(nèi)存偷仿。

禁止進(jìn)行指令重排序

具體一點(diǎn)解釋,禁止重排序的規(guī)則如下:

  • 當(dāng)程序執(zhí)行到 volatile變量的讀操作或者寫(xiě)操作時(shí)绵估,在其前面的操作的更改肯定全部已經(jīng)進(jìn)行炎疆,且結(jié)果已經(jīng)對(duì)后面的操作可見(jiàn);在其后面的操作肯定還沒(méi)有進(jìn)行国裳;
  • 在進(jìn)行指令優(yōu)化時(shí)形入,不能將在對(duì) volatile 變量訪問(wèn)的語(yǔ)句放在其后面執(zhí)行,也不能把 volatile 變量后面的語(yǔ)句放到其前面執(zhí)行缝左。

普通的變量?jī)H僅會(huì)保證該方法的執(zhí)行過(guò)程中所有依賴賦值結(jié)果的地方都能獲取到正確的結(jié)果亿遂,而不能保證賦值操作的順序與程序代碼中的執(zhí)行順序一致浓若。

volatile型變量實(shí)現(xiàn)原理

具體實(shí)現(xiàn)方式是在編譯期生成字節(jié)碼時(shí),會(huì)在指令序列中增加內(nèi)存屏障蛇数,內(nèi)存屏障提供了以下功能:

  1. 重排序時(shí)不能把后面的指令重排序到內(nèi)存屏障之前的位置
  2. 使得本CPU的Cache寫(xiě)入內(nèi)存
  3. 寫(xiě)入動(dòng)作也會(huì)引起別的CPU或者別的內(nèi)核無(wú)效化其Cache挪钓,相當(dāng)于讓新寫(xiě)入的值對(duì)別的線程可見(jiàn)。

下面是基于保守策略的JMM內(nèi)存屏障插入策略:

  • 在每個(gè)volatile寫(xiě)操作的前面插入一個(gè)StoreStore屏障耳舅。
    該屏障除了保證了屏障之前的寫(xiě)操作和該屏障之后的寫(xiě)操作不能重排序碌上,還會(huì)保證了volatile寫(xiě)操作之前,任何的讀寫(xiě)操作都會(huì)先于volatile被提交浦徊。
  • 在每個(gè)volatile寫(xiě)操作的后面插入一個(gè)StoreLoad屏障馏予。
    該屏障除了使volatile寫(xiě)操作不會(huì)與之后的讀操作重排序外,還會(huì)刷新處理器緩存盔性,使volatile變量的寫(xiě)更新對(duì)其他線程可見(jiàn)霞丧。
  • 在每個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障。
    該屏障除了使volatile讀操作不會(huì)與之前的寫(xiě)操作發(fā)生重排序外冕香,還會(huì)刷新處理器緩存蛹尝,使volatile變量讀取的為最新值。
  • 在每個(gè)volatile讀操作的后面插入一個(gè)LoadStore屏障悉尾。
    該屏障除了禁止了volatile讀操作與其之后的任何寫(xiě)操作進(jìn)行重排序突那,還會(huì)刷新處理器緩存,使其他線程volatile變量的寫(xiě)更新對(duì)volatile讀操作的線程可見(jiàn)焕襟。

volatile型變量使用場(chǎng)景

“一次寫(xiě)入陨收,到處讀取”,某一線程負(fù)責(zé)更新變量鸵赖,其他線程只讀取變量(不更新變量)务漩,并根據(jù)變量的新值執(zhí)行相應(yīng)邏輯。例如狀態(tài)標(biāo)志位更新它褪,觀察者模型變量值發(fā)布饵骨。

synchronized

通過(guò) synchronized關(guān)鍵字包住的代碼區(qū)域,對(duì)數(shù)據(jù)的讀寫(xiě)進(jìn)行控制:

  • 讀數(shù)據(jù)
    當(dāng)線程進(jìn)入到該區(qū)域讀取變量信息時(shí)茫打,對(duì)數(shù)據(jù)的讀取也不能從工作內(nèi)存讀取居触,只能從內(nèi)存中讀取,保證讀到的是最新的值老赤。

  • 寫(xiě)數(shù)據(jù)
    在同步區(qū)內(nèi)對(duì)變量的寫(xiě)入操作轮洋,在離開(kāi)同步區(qū)時(shí)就將當(dāng)前線程內(nèi)的數(shù)據(jù)刷新到內(nèi)存中,保證更新的數(shù)據(jù)對(duì)其他線程的可見(jiàn)性抬旺。

synchronized 修飾實(shí)例方法

public class TestSynchronized {

    private int total = 0;

    public synchronized void addTotal(){
        total++;
    }
}

這種情況下的鎖對(duì)象是當(dāng)前實(shí)例對(duì)象弊予,因此只有同一個(gè)實(shí)例對(duì)象調(diào)用此方法才會(huì)產(chǎn)生互斥效果,不同實(shí)例對(duì)象之間不會(huì)有互斥效果开财。比如如下代碼:

public class Test {


    public static void main(String[] args) {
        final TestSynchronized ts1 = new TestSynchronized();
        final TestSynchronized ts2 = new TestSynchronized();

        Thread t1 = new Thread(){
            public void run(){
                ts1.print();
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                ts2.print();
            }
        };
        t1.start();
        t2.start();
    }
}
public class TestSynchronized {

    private int total = 0;

    public synchronized void addTotal() {
        total++;
    }

    public synchronized void print() {
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " is printing ----- " + i);
                Thread.sleep(300);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上述代碼汉柒,在不同的線程中調(diào)用的是不同對(duì)象的 print 方法误褪,因此彼此之間不會(huì)有排斥。運(yùn)行效果如下:

可以看出碾褂,兩個(gè)線程是交互執(zhí)行的兽间。

如果將代碼進(jìn)行如下修改,兩個(gè)線程調(diào)用同一個(gè)對(duì)象的 print 方法:

則執(zhí)行效果如下:

可以看出:只有某一個(gè)線程中的代碼執(zhí)行完之后正塌,才會(huì)調(diào)用另一個(gè)線程中的代碼嘀略。也就是說(shuō)此時(shí)兩個(gè)線程間是互斥的。

synchronized 修飾靜態(tài)類(lèi)方法

如果 synchronized 修飾的是靜態(tài)方法传货,則鎖對(duì)象是當(dāng)前類(lèi)的 Class 對(duì)象屎鳍。因此即使在不同線程中調(diào)用不同實(shí)例對(duì)象,也會(huì)有互斥效果问裕。

將 print 修改為靜態(tài)方法,如下:


執(zhí)行后的打印效果如下:

從執(zhí)行的結(jié)果可以看出孵坚,這兩個(gè)線程也是互斥的粮宛。

synchronized 修飾代碼塊

除了直接修飾方法之外,synchronized 還可以作用于代碼塊卖宠,如下代碼所示:

執(zhí)行后的打印效果如下:

synchronized 作用于代碼塊時(shí)巍杈,鎖對(duì)象就是跟在后面括號(hào)中的對(duì)象,任何 Object 對(duì)象都可以當(dāng)作鎖對(duì)象扛伍。

synchronized 實(shí)現(xiàn)細(xì)節(jié)

使用 synchronized 作用于代碼塊:

使用 javap 查看上述 test1 方法的字節(jié)碼筷畦,可以看出,編譯而成的字節(jié)碼中會(huì)包含 monitorenter 和 monitorexit 這兩個(gè)字節(jié)碼指令刺洒。如下所示:

上面字節(jié)碼中有 1 個(gè) monitorenter 和 2 個(gè) monitorexit鳖宾。這是因?yàn)樘摂M機(jī)需要保證當(dāng)異常發(fā)生時(shí)也能釋放鎖。因此 2 個(gè) monitorexit 一個(gè)是代碼正常執(zhí)行結(jié)束后釋放鎖逆航,一個(gè)是在代碼執(zhí)行異常時(shí)釋放鎖鼎文。

使用 synchronized 修飾方法:

從圖中可以看出,被 synchronized 修飾的方法在被編譯為字節(jié)碼后因俐,在方法的 flags 屬性中會(huì)被標(biāo)記為 ACC_SYNCHRONIZED 標(biāo)志拇惋。當(dāng)虛擬機(jī)訪問(wèn)一個(gè)被標(biāo)記為 ACC_SYNCHRONIZED 的方法時(shí),會(huì)自動(dòng)在方法的開(kāi)始和結(jié)束(或異常)位置添加 monitorenter 和 monitorexit 指令抹剩。

關(guān)于 monitorenter 和 monitorexit撑帖,可以理解為一把具體的鎖。在這個(gè)鎖中保存著兩個(gè)比較重要的屬性:計(jì)數(shù)器和指針澳眷。計(jì)數(shù)器代表當(dāng)前線程一共訪問(wèn)了幾次這把鎖胡嘿;指針指向持有這把鎖的線程。

鎖計(jì)數(shù)器默認(rèn)為0境蔼,當(dāng)執(zhí)行monitorenter指令時(shí)灶平,如鎖計(jì)數(shù)器值為0 說(shuō)明這把鎖并沒(méi)有被其它線程持有伺通。那么這個(gè)線程會(huì)將計(jì)數(shù)器加1,并將鎖中的指針指向自己逢享。當(dāng)執(zhí)行monitorexit指令時(shí)罐监,會(huì)將計(jì)數(shù)器減1。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瞒爬,一起剝皮案震驚了整個(gè)濱河市弓柱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌侧但,老刑警劉巖矢空,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異禀横,居然都是意外死亡屁药,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)柏锄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)酿箭,“玉大人,你說(shuō)我怎么就攤上這事趾娃$缘眨” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵抬闷,是天一觀的道長(zhǎng)妇蛀。 經(jīng)常有香客問(wèn)我,道長(zhǎng)笤成,這世上最難降的妖魔是什么评架? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮疹启,結(jié)果婚禮上古程,老公的妹妹穿的比我還像新娘。我一直安慰自己喊崖,他們只是感情好挣磨,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著荤懂,像睡著了一般茁裙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上节仿,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天晤锥,我揣著相機(jī)與錄音,去河邊找鬼。 笑死矾瘾,一個(gè)胖子當(dāng)著我的面吹牛女轿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播壕翩,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蛉迹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了放妈?” 一聲冷哼從身側(cè)響起北救,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎芜抒,沒(méi)想到半個(gè)月后珍策,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宅倒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年攘宙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拐迁。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡模聋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出唠亚,到底是詐尸還是另有隱情,我是刑警寧澤持痰,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布灶搜,位于F島的核電站,受9級(jí)特大地震影響工窍,放射性物質(zhì)發(fā)生泄漏割卖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一患雏、第九天 我趴在偏房一處隱蔽的房頂上張望鹏溯。 院中可真熱鬧,春花似錦淹仑、人聲如沸丙挽。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)颜阐。三九已至,卻和暖如春吓肋,著一層夾襖步出監(jiān)牢的瞬間凳怨,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肤舞,地道東北人紫新。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像李剖,于是被迫代替她去往敵國(guó)和親芒率。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345