Java內(nèi)存模型-筆記5

  • 硬件內(nèi)存模型
  • Java內(nèi)存模型
  • 線程之間通信
    • 同步性原則
  • 可能出現(xiàn)的問題
    • 可見性
    • 原子性
    • 有序性

硬件內(nèi)存模型

工程師為了追求橫向的拓展庐冯,就是在單臺(tái)計(jì)算機(jī)中使用更多的處理器孽亲。

眾所周知,目前CPU的處理器速度與內(nèi)存速度的讀寫速度不在一個(gè)數(shù)量級(jí)展父,所以需要在CPU和內(nèi)存之間加上緩存來進(jìn)行提速返劲,這樣的就呈現(xiàn)了一種CPU-寄存器-緩存-主存的訪問結(jié)構(gòu)。

cpu:包含運(yùn)算器和控制器栖茉。根據(jù)馮諾依曼體系篮绿,CPU的工作分為以下 5 個(gè)階段:取指令階段、指令譯碼階段吕漂、執(zhí)行指令階段亲配、訪存取數(shù)和結(jié)果寫回。

寄存器: 指令寄存器痰娱、程序計(jì)數(shù)器等

結(jié)構(gòu): CPU-寄存器-緩存-主存

這種結(jié)構(gòu)在單CPU時(shí)期運(yùn)行的很好弃榨,但是當(dāng)一臺(tái)計(jì)算機(jī)中引入了多個(gè)CPU時(shí),出現(xiàn)了一個(gè)棘手的問題梨睁,假如CPU A將數(shù)據(jù)D從主存讀取到獨(dú)占的緩存內(nèi)鲸睛,通過計(jì)算之后修改了數(shù)據(jù)D,變?yōu)镈1坡贺,但是還沒有刷新回到主存官辈,此時(shí)CPU B將數(shù)據(jù)D從主存讀取到獨(dú)占緩存內(nèi),也對D進(jìn)行計(jì)算變?yōu)镈2遍坟,顯而易見這時(shí)候的數(shù)據(jù)產(chǎn)生了不同步拳亿。

到底是以D1為準(zhǔn)還是以D2為準(zhǔn),針對這個(gè)問題愿伴,科學(xué)家們設(shè)計(jì)了緩存一致性協(xié)議肺魁。

image-20220430182347162.png

主要就是為了解決多個(gè)CPU緩存之間的同步問題,CPU緩存一致性協(xié)議有很多隔节,大致可以分為兩類鹅经。

窺探型和基于目錄型寂呛,當(dāng)CPU緩存想要訪問主存時(shí),需要經(jīng)過一致性協(xié)議這種軟件層面的措施來保證數(shù)據(jù)的一致性瘾晃,協(xié)議本事的實(shí)現(xiàn)細(xì)節(jié)贷痪,可以猜想的是,其中的內(nèi)容一定是一些和數(shù)據(jù)同步相關(guān)的操作蹦误,既然要進(jìn)行數(shù)據(jù)同步劫拢,很可能出現(xiàn)等待喚醒這樣的措施,這將可能導(dǎo)致性能問題强胰,尤其是對于CPU這種運(yùn)算速度極快的組件來說舱沧,絲毫的等待都是極大的浪費(fèi),比如CPU B想要讀取數(shù)據(jù) D的時(shí)候哪廓,還需要等待CPU A將D寫回主存狗唉,這種行為是難以忍受的,因此涡真,計(jì)算機(jī)科學(xué)家們做出了一些優(yōu)化分俯,整體思路上就是將同步改為異步,比如CPU B要讀取數(shù)據(jù)D時(shí)哆料,發(fā)現(xiàn)D正在被其他的CPU修改缸剪,那么此時(shí)CPU B 可以注冊一個(gè)讀取D的消息,自己能回頭去做其他事情东亦,其他CPU寫會(huì)數(shù)據(jù)D后杏节,響應(yīng)了這個(gè)注冊消息,此時(shí)CPU B發(fā)現(xiàn)消息被響應(yīng)后典阵,再去讀取D 這樣的就能夠提升效率奋渔。但是對于CPU B來說,程序看上去就不是順序執(zhí)行了壮啊,可能會(huì)出現(xiàn)先運(yùn)行后面的指令嫉鲸,再回頭去運(yùn)行前面的指令,這一種行為就體現(xiàn)出了一種指令重排序歹啼。雖然指令被重排了玄渗,但CPU依然需要保證程序執(zhí)行結(jié)果的正確性,就是說無論指令怎么重排狸眼,最后的執(zhí)行結(jié)果一定要和順序執(zhí)行的結(jié)果是一樣的藤树,這具體是如何實(shí)現(xiàn)的呢?可以做一個(gè)擴(kuò)展

指令重排相關(guān)知識(shí)點(diǎn)(了解)

1.Store Buffer

2.Store Forwarding

3.Invalid Queue

4.寫屏障

5.內(nèi)存屏障

硬件內(nèi)存模型的目標(biāo)是為了讓匯編代碼能夠運(yùn)行在一個(gè)具有一致性的內(nèi)存視圖上拓萌。隨著高級(jí)語言的流行岁钓。工程師們開始設(shè)計(jì)編程語言級(jí)別的內(nèi)存模型,這是為了能夠使用該語言編程的也能擁有一個(gè)一致性的內(nèi)存視圖。

一致性的內(nèi)存視圖甜紫。各種硬件內(nèi)存模型抽象出相同的內(nèi)存視圖降宅。

Java內(nèi)存模型

于是在硬件模型之上,還存在著為編程語言設(shè)計(jì)的內(nèi)存模型囚霸,比如Java內(nèi)存模型 JMM (Java Memory Model)就屏蔽了各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,實(shí)現(xiàn)了讓Java程序能夠在各種硬件平臺(tái)下激才,都能夠按照預(yù)期的方式來運(yùn)行拓型。

他的抽象,如圖瘸恼,

image-20220430184342044.png

概括來說每個(gè)工作流程都擁有獨(dú)占的本地內(nèi)存劣挫,本地內(nèi)存中的存儲(chǔ)的是私有變量以及共享變量的副本,并且使用一定機(jī)制來控制本地內(nèi)存和主存之間讀寫數(shù)據(jù)時(shí)的同步問題东帅,更加具體一點(diǎn)压固,我們將工作線程和本地內(nèi)存具象為 thread stack 將主存具象為heap 。

image-20220430185027769.png

Thread stack中有兩種類型的變量靠闭。其中原始類型的變量帐我,總是存儲(chǔ)在線程棧上,對象類型的變量 引用或者說指針本身是存儲(chǔ)在線程棧上愧膀,而引用指向的對象的是存儲(chǔ)在堆上的拦键。在Heap中存儲(chǔ)對象本身,持有對象引用的線程就都能夠訪問該對象檩淋,heap本身他不關(guān)心哪個(gè)線程正在訪問對象

我們可以這么理解 Java線程模型中的thread stack 和heap都是對物理內(nèi)存的一種抽象芬为。這樣開發(fā)者只需要關(guān)心自己寫的程序使用到了thread stack/heap ,而不需要關(guān)心更下層的寄存器 cpu緩存 主存蟀悦∶碾可以猜測,線程在工作時(shí)的大部分情況下都在讀寫thread stack中的本地內(nèi)存日戈,也就是說本地內(nèi)存對速度的要求更高询张,那么他可能大部分都是使用寄存器和CPU緩存來實(shí)現(xiàn)的,而heap中需要存儲(chǔ)大量的對象涎拉,需要更大的容量瑞侮。那么他可能大部分都是使用主存來實(shí)現(xiàn)的。

image-20220430190514847.png

線程之間通信

這樣想來鼓拧,大概就能理解Java內(nèi)存模型與硬件內(nèi)存模型之間這種模糊的內(nèi)容映射關(guān)系了半火。上面我們提到了Java內(nèi)存模型需要設(shè)計(jì)一些機(jī)制,來實(shí)現(xiàn)主存與工作內(nèi)存之間的數(shù)據(jù)傳輸與同步季俩,這種數(shù)據(jù)的傳遞钮糖,正式線程之間的通信方式。

主存和工作內(nèi)存之間通過這八個(gè)指令來實(shí)現(xiàn)數(shù)據(jù)的讀寫與同步,按照作用域分別分為兩類:

一類是作用于主存店归,一類是作用于工作內(nèi)存阎抒。下圖是一個(gè)通信的例子:

image-20220430192610266.png

比如說線程A現(xiàn)在調(diào)用lock指令,將x變量標(biāo)記獨(dú)占狀態(tài)消痛,接下來他assign/store兩個(gè)指令來對x進(jìn)行賦值且叁,使他變?yōu)? 再繼續(xù)調(diào)用write指令,將x這個(gè)變量寫入主存秩伞,此時(shí)A的操作已經(jīng)完成逞带,并進(jìn)行解鎖,于是調(diào)用了unlock指令釋放x鎖定狀態(tài) 這時(shí)候呢纱新,線程B要讀取變量X展氓,于是他調(diào)用了read指令來讀取了x這個(gè)變量,調(diào)用load將變量加載到自己的本地內(nèi)存中脸爱,最后他再調(diào)用use指令來讓計(jì)算資源對這個(gè)變量進(jìn)行操作遇汞。這一套下來就實(shí)現(xiàn)了線程A和線程B之間的通信。不過這張圖上演示的是一種比較理想的狀態(tài)簿废。而實(shí)際的線程通信中還存在著一些問題需要解決空入。

可能出現(xiàn)的問題

第一個(gè)問題:

假如本地內(nèi)存A和本地內(nèi)存B中存在x副本且值都是1,當(dāng)線程A將x修改為2并且寫入主存后捏鱼,此時(shí)線程B想要讀取x执庐,默認(rèn)會(huì)從本地內(nèi)存B中讀取,而本地內(nèi)存B中的x依然是等于1的导梆,換言之轨淌,線程A刷新了主存中的x,線程B如何才能讀取到最新的值看尼,那么這個(gè)問題被稱為一種可見性的問題递鹉。

第二個(gè)問題:

加入線程A和B都從主存中讀取了變量x,此時(shí)x=1藏斩,分別在各自的本地內(nèi)存中自增1,x變?yōu)榱?躏结,然后再刷新回主存,這里就有一個(gè)問題狰域,實(shí)際上自增了兩次媳拴,x應(yīng)該變?yōu)?,但是主存中的x 卻為2兆览。那么這種問題被稱為一種原子性的問題屈溉。

上面所說的兩個(gè)問題其實(shí)就是反應(yīng)了線程通信之間的同步問題。當(dāng)多個(gè)線程在并發(fā)操作共享數(shù)據(jù)時(shí)抬探,可能回引發(fā)各種各樣的問題子巾。這些問題,被總結(jié)為三個(gè)要素。 可見性 原子性 有序性

上面所說的兩個(gè)問題呢线梗,分別對應(yīng)可見性和原子性椰于,這三個(gè)要素事實(shí)上并不是完全割裂的,尤其是可見性和有序性仪搔。

可見性

可見性指的是:當(dāng)一個(gè)線程修改共享變量的值瘾婿,其他線程需要能夠立刻得知這個(gè)修改。

這句話其實(shí)有兩層含義:

1.線程A修改了數(shù)據(jù)D僻造,線程B需要督導(dǎo)修改后最新的D憋他。(由刷新主存的時(shí)機(jī)引起的)

對應(yīng)到Java內(nèi)存模型中,當(dāng)一個(gè)線程在自己的工作內(nèi)存中修改了某個(gè)變量髓削,應(yīng)該把該變量立即刷新到主存中,并讓其他線程知道镀娶。

對應(yīng)的代碼:

    static int a = 1;

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Runnable() {

            @SneakyThrows
            @Override
            public void run() {
                while (a != 2) {
                    // do nothing
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {

            @Override
            public void run() {
                a = 2;
            }
        });

        thread1.start();
        Thread.sleep(1000);
        thread2.start();
    }

當(dāng)我們執(zhí)行main方法時(shí)立膛,首先線程1會(huì)啟動(dòng),由于a的值為1梯码,線程1將會(huì)執(zhí)行死循環(huán)宝泵, 一秒后線程2啟動(dòng)。線程2將a的值改為2轩娶,此時(shí)如果線程1能夠讀到a的值被修改為2的話儿奶,將會(huì)跳出死循環(huán),但是你會(huì)發(fā)現(xiàn)事實(shí)上并沒有跳出鳄抒,死循環(huán)將一致執(zhí)行下去闯捎。說明變量a的修改并沒有被線程1讀到,那么說明a此時(shí)不滿足可見性许溅,針對此種情況瓤鼻,如何解決呢?

當(dāng)某個(gè)線程修改了變量贤重,其他線程如何才能立刻獲取到最新值茬祷,這里主要由兩種解決辦法。

第一種:利用volataile關(guān)鍵字并蝗。volatile關(guān)鍵字的下層實(shí)現(xiàn)保證了祭犯,若一個(gè)被volatile寫volatile修飾的變量被修改,那么總會(huì)主動(dòng)寫入主存滚停,若要讀取一個(gè)volatile變量沃粗,那么總是從主存中讀取。這樣的話铐刘,相當(dāng)于操作volatile變量都是直接去讀寫主存陪每。這樣就能夠解決上面的可見性問題。

第二種:利用Synchronized關(guān)鍵字,Synchronized關(guān)鍵字實(shí)現(xiàn)的一個(gè)特性檩禾。在同步代碼塊中挂签,monitor的基礎(chǔ)上,讀寫變量時(shí)盼产,將會(huì)隱式地執(zhí)行上文提到的內(nèi)存lock指令饵婆,并清空工作內(nèi)存中該變量的值,需要使用該變量時(shí)必須從主存中讀取戏售。同理侨核,也會(huì)隱式的執(zhí)行內(nèi)存unlock指令,將修改過的變量刷新回主存灌灾。這樣也能夠解決可見性問題搓译。

對應(yīng)的代碼:

    static  int a = 1;

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Runnable() {

            @SneakyThrows
            @Override
            public void run() {
                while (a != 2) {
                    synchronized (this) {
                        int b = a + 1;
                    }
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {

            @Override
            public void run() {
                a = 2;
            }
        });

        thread1.start();
        Thread.sleep(1000);
        thread2.start();
    }

2.第二種可見性問題:線程B需要讀到被修改的變量D,線程A應(yīng)該修改锋喜,但是因?yàn)橹嘏判驅(qū)е戮€程A沒有及時(shí)修改變量D些己。(由指令重排引起的)

代碼:

    static int a = 0;
    static boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Runnable() {

            @SneakyThrows
            @Override
            public void run() {
                a = 1; // 1
                flag = true; // 2
            }
        });

        Thread thread2 = new Thread(new Runnable() {

            @Override
            public void run() {
                if (flag) { // 3
                    int i = a; // 4
                }
            }
        });

        thread1.start();
        thread2.start();
    }

如果代碼執(zhí)行到注釋4這行時(shí),變量i是否一定等于1嘿般,答案是否定的段标,我們在最上面提到過,硬件內(nèi)存模型中存在指令重排序機(jī)制炉奴,Java內(nèi)存模型中也存在指令重排逼庞,他們的作用和約束都是一樣的,第一是為了更高的執(zhí)行效率瞻赶,第二個(gè)在單線程中指令重排后能夠保證程序執(zhí)行結(jié)果的正確性赛糟,就是說和順序執(zhí)行的結(jié)果是一樣的。所以線程一中的代碼一和代碼二完全有可能在編譯后被重排共耍,出現(xiàn)了下面這樣的執(zhí)行順序 代碼2->代碼3->代碼4->代碼1虑灰。在這種情況下,變量i還是等于0痹兜,但是從程序順序執(zhí)行的邏輯上看穆咐,似乎只要執(zhí)行到代碼4,變量i的值就一定是1字旭,這里就出現(xiàn)了可見性問題对湃,說明變量a此時(shí)不滿足可見性。

同樣的遗淳,我們也可以通過volatile和sync這兩個(gè)關(guān)鍵字來解決這種可見性問題拍柒。第一種,使用volatile關(guān)鍵字屈暗,volatile關(guān)鍵字禁止當(dāng)前變量與之前的代碼語句進(jìn)行重排序拆讯,可以這么理解脂男,當(dāng)程序執(zhí)行到volatile變量的讀寫時(shí)(還未執(zhí)行),之前的代碼語句的執(zhí)行結(jié)果是滿足可見性的种呐。當(dāng)執(zhí)行volatile的讀寫時(shí)宰翅,上文講過變量將會(huì)與主存進(jìn)行同步,所以volatile變量保證了可見性爽室。

在這個(gè)例子中汁讼,我們只要給付那個(gè)變量加上volatile修飾,那么就能夠禁止代碼1和代碼2的重排阔墩,因?yàn)榇a2中的變量是被volatile修飾的嘿架,根據(jù)上一段所說,就能夠保證代碼1的可見性啸箫。線程2中的代碼4就能夠成功的讀到a的值為1耸彪,synchronized關(guān)鍵字,我們再看上面的這個(gè)例子導(dǎo)致可見性問題的根源就是代碼1和代碼2被重排了忘苛,并且在執(zhí)行期間線程2讀到了線程1的中間狀態(tài)搜囱,那么如果代碼1和代碼2變成了一個(gè)不可分割的代碼塊,這時(shí)無論其內(nèi)部如何進(jìn)行重排柑土,外部都只能讀到最終結(jié)果,所以也就避免了可見性的問題绊汹。

特別提醒:Java的指令重排有兩次稽屏,第一次發(fā)生在將字節(jié)碼編譯成機(jī)器碼的階段,第二次發(fā)生在CPU執(zhí)行的時(shí)候西乖,也會(huì)適當(dāng)?shù)倪M(jìn)行指令重排狐榔。

關(guān)于指令重排的的復(fù)現(xiàn)代碼:

package com.example.demo0413.test;

public class VolatileReOrderSample {
    //定義四個(gè)靜態(tài)變量
    private static int x=0,y=0;
    private static int a=0,b=0;

    public static void main(String[] args) throws InterruptedException {
        int i=0;
        while (true){
            i++;
            x=0;y=0;a=0;b=0;
            //開兩個(gè)線程,第一個(gè)線程執(zhí)行a=1;x=b;第二個(gè)線程執(zhí)行b=1;y=a
            Thread thread1=new Thread(new Runnable() {
                @Override
                public void run() {
                    //線程1會(huì)比線程2先執(zhí)行获雕,因此用nanoTime讓線程1等待線程2 0.01毫秒
                    shortWait(10000);
                    a=1;
                    x=b;
                }
            });
            Thread thread2=new Thread(new Runnable() {
                @Override
                public void run() {
                    b=1;
                    y=a;
                }
            });
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            //等兩個(gè)線程都執(zhí)行完畢后拼接結(jié)果
            String result="第"+i+"次執(zhí)行x="+x+"y="+y;
            //如果x=0且y=0薄腻,則跳出循環(huán)
            if (x==0&&y==0){
                System.out.println(result);
                break;
            }else{
                System.out.println(result);
            }
        }
    }
    //等待interval納秒
    private static void shortWait(long interval) {
        long start=System.nanoTime();
        long end;
        do {
            end=System.nanoTime();
        }while (start+interval>=end);
    }
}

happens-before原則

設(shè)計(jì)內(nèi)存的前輩們?yōu)槲覀兘鉀Q了這些事,他們定義了一組原則届案,被稱為 happens-before原則庵楷,這組原則規(guī)定了對于兩個(gè)操作A和B 這兩個(gè)操作可以在不同的線程中執(zhí)行。如果A happens-before B 那么可以保證 當(dāng)A操作執(zhí)行完后楣颠,A操作的執(zhí)行結(jié)果對 B操作是可見的尽纽。事實(shí)上,之所以很多人在日常開發(fā)中對可見性問題沒有太多的感知童漩,那是因?yàn)樵诓恢挥X中就已經(jīng)滿足了happens-before原則之一弄贿。

這個(gè)原則有八條:

  • 程序順序規(guī)則

  • 鎖定規(guī)則

  • volatile變量規(guī)則

  • 線程啟動(dòng)規(guī)則

  • 線程結(jié)束規(guī)則

  • 中斷規(guī)則

  • 終結(jié)期規(guī)則

  • 傳遞性規(guī)則

前面比較重要的三條,

程序順序原則矫膨,

在一個(gè)線程的內(nèi)部按照程序代碼的書寫順序差凹,書寫在前面的代碼操作 happens-before于書寫在后面的代碼操作期奔。因?yàn)樵趩蝹€(gè)線程中程序員編寫的代碼在語義上是需要穿行順序地執(zhí)行,即使在編譯后的代碼可能會(huì)進(jìn)行重排危尿,但是內(nèi)存模型會(huì)保證程序執(zhí)行結(jié)果的正確性呐萌。也就是說,無論他的內(nèi)部怎么重排脚线,他最終的執(zhí)行結(jié)果和順序執(zhí)行的結(jié)果是一致的搁胆。這也是大部分程序員在執(zhí)行自己所寫的代碼時(shí)沒有出現(xiàn)可見性問題的主要原因。

鎖定規(guī)則邮绿,

對于一個(gè)鎖的解鎖渠旁,總是happens-before這個(gè)鎖的加鎖。synchronized保證可見性的主要原理就是刷新儲(chǔ)存和原子化多個(gè)操作船逮。

volatile規(guī)則顾腊,

對于一個(gè)volatile變量的寫,總是happens-before 于后續(xù)對這個(gè)volatile變量的讀挖胃,其中的原理主要是刷新主存和禁止重排序杂靶。

原子性

原子性指的是 一個(gè)操作是不可中斷的,要么全部執(zhí)行成功酱鸭,要么全部執(zhí)行失敗吗垮。原子操作我按照自己的理解分為兩種,一種是單指令原子操作凹髓,單指令原子操作指的是烁登,當(dāng)你執(zhí)行單個(gè)指令,要么成功要么失敗蔚舀,比如工作內(nèi)存和主存之間進(jìn)行讀寫的8個(gè)指令饵沧,這些指令是不可再分的,每個(gè)指令都是原子操作赌躺。第二種利用鎖的組合指令原子操作狼牺,有時(shí)候開發(fā)者想讓一組操作要么執(zhí)行成功,要么執(zhí)行失敗礼患,也就是想要保證一組指令的原子性是钥,這時(shí)候就要用到鎖,比如8個(gè)內(nèi)存指令中就有l(wèi)ock和unlock這兩個(gè)和鎖有關(guān)的指令讶泰。利用他們咏瑟,可以支持一組指令的原子性,反應(yīng)到上層就是synchronized痪署。

有序性

無論是從硬件內(nèi)存模型還是Java內(nèi)存模型來看码泞,都支持指令重排這種優(yōu)化操作,在單線程中雖然指令可能會(huì)被重排狼犯,但是在單線程中內(nèi)存模型能夠保證執(zhí)行結(jié)果的準(zhǔn)確余寥。也就是說在單線程中無論指令如何重排领铐,他最終的執(zhí)行結(jié)果和順序執(zhí)行的結(jié)果是一樣的,但是在多線程環(huán)境下就可能因?yàn)橹噶钪嘏哦鴮?dǎo)致一些問題宋舷。

有序性和可見性是不能完全分開講的绪撵,指令重排引起的亂序最有可能導(dǎo)致的就是可見性問題。我們之前又說happens-before原則來解決部分由于重排而導(dǎo)致的可見性問題祝蝠,并且針對volatile原則和鎖原則音诈,他們?yōu)槭裁纯梢詫?shí)現(xiàn)內(nèi)部可見性的原理。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绎狭,一起剝皮案震驚了整個(gè)濱河市细溅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌儡嘶,老刑警劉巖喇聊,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蹦狂,居然都是意外死亡誓篱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門凯楔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窜骄,“玉大人,你說我怎么就攤上這事摆屯“⊙校” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵鸥拧,是天一觀的道長。 經(jīng)常有香客問我削解,道長富弦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任氛驮,我火速辦了婚禮腕柜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘矫废。我一直安慰自己盏缤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布蓖扑。 她就那樣靜靜地躺著唉铜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪律杠。 梳的紋絲不亂的頭發(fā)上潭流,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天竞惋,我揣著相機(jī)與錄音,去河邊找鬼灰嫉。 笑死拆宛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的讼撒。 我是一名探鬼主播浑厚,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼根盒!你這毒婦竟也來了撼唾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對情侶失蹤边琉,失蹤者是張志新(化名)和其女友劉穎倘屹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厂榛,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盖矫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了击奶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辈双。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖柜砾,靈堂內(nèi)的尸體忽然破棺而出湃望,到底是詐尸還是另有隱情,我是刑警寧澤痰驱,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布证芭,位于F島的核電站,受9級(jí)特大地震影響担映,放射性物質(zhì)發(fā)生泄漏废士。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一蝇完、第九天 我趴在偏房一處隱蔽的房頂上張望官硝。 院中可真熱鬧,春花似錦短蜕、人聲如沸氢架。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岖研。三九已至,卻和暖如春警检,著一層夾襖步出監(jiān)牢的瞬間缎玫,已是汗流浹背硬纤。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赃磨,地道東北人筝家。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像邻辉,于是被迫代替她去往敵國和親溪王。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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