android 多線(xiàn)程 — handle 學(xué)習(xí)

為啥要有 handle


首先 android UI 線(xiàn)程的類(lèi)型是 ActivityThread皂贩,這可能在這里沒(méi)什么用,湊湊字?jǐn)?shù)吧......

  1. android 的 UI 控件不是線(xiàn)程安全的十籍, 多線(xiàn)程并發(fā)訪(fǎng)問(wèn) UI 控件時(shí)可能會(huì)產(chǎn)生問(wèn)題。
  2. 為什么不給 UI 控件加鎖,一是加鎖會(huì)復(fù)雜很多界轩,二是加鎖會(huì)阻塞其他訪(fǎng)問(wèn) UI 的線(xiàn)程盐须,有可能造成別的線(xiàn)程占用 UI 而把 UI 線(xiàn)程阻塞了玩荠,這就肯定會(huì)造成卡頓問(wèn)題了。所以才采用了單線(xiàn)程更新 UI 的模式贼邓,使用 handle 來(lái)切換線(xiàn)程阶冈。

上面的解釋是看:開(kāi)發(fā)藝術(shù)探索 總結(jié)的......

handle 中幾個(gè)角色:


  • ThreadLocal
    每個(gè)線(xiàn)程中用來(lái)保存私有變量的容器
  • Looper
    消息隊(duì)列的管理容器,也可以叫輪詢(xún)器
  • MessageQueue
    消息隊(duì)列
  • Message
    消息本身
  • handle
    消息發(fā)送器塑径,和消息消費(fèi)者

說(shuō)下過(guò)程:


  1. Looper.prepare();
    looper 的初始化創(chuàng)建女坑,looper 會(huì)創(chuàng)建自己,每個(gè) Looper 對(duì)象的創(chuàng)建都會(huì)伴隨創(chuàng)建一個(gè)消息隊(duì)列 MessageQueue统舀,并把自己保存在當(dāng)前線(xiàn)程的 ThreadLocal 中匆骗,保證每個(gè)線(xiàn)程中 looper 的唯一性。
  2. Looper.loop();
    looper 開(kāi)始一個(gè)無(wú)限循環(huán)绑咱,從內(nèi)部的 MessageQueue 消息隊(duì)列中循環(huán)取出數(shù)據(jù)挨個(gè)執(zhí)行绰筛,消息隊(duì)列沒(méi)有數(shù)據(jù)了就會(huì)掛起。
  3. message.getTarget().dispatchMessage(message);
    消息最終就是這么被執(zhí)行的描融, message.getTarget() 的返回的對(duì)象就是 handle 铝噩,所以這里由 handle 會(huì)出現(xiàn)內(nèi)存泄露。
  4. handler.sendMessage();
    handle 發(fā)送數(shù)據(jù)窿克,就是把自己傳遞給這個(gè)待處理的消息 message 中骏庸,然后添加到 MessageQueue 消息隊(duì)列里面去。
  5. Handler handler2 = new Handler(Looper.getMainLooper());
    Looper里面有個(gè)靜態(tài)的 looper 年叮,就是當(dāng)前進(jìn)程中 UI 線(xiàn)程的 looper具被,通過(guò)這個(gè) UI 的線(xiàn)程的 looper ,我們可以創(chuàng)建一個(gè) handle 出來(lái)只损,然后添加消息到主進(jìn)程中去執(zhí)行一姿。

為啥 looper 可以切換線(xiàn)程


looper 的都是要求我們?cè)? Looper.prepare() looper 的初始化之后馬上 Looper.loop() 讓 looper 跑起來(lái)的七咧,lopper 本身是一個(gè)無(wú)限循環(huán),會(huì)一直在 looper 所在線(xiàn)程中執(zhí)行叮叹,所以我們通過(guò)不同的 looper 對(duì)象艾栋,創(chuàng)建 handle 對(duì)象發(fā)送消失時(shí)都是把這個(gè)消息發(fā)送到了對(duì)應(yīng) looper 所以在的 MessageQueue 消息隊(duì)列中去,這個(gè)消息隊(duì)列自然的會(huì)在所訴的那個(gè) looper 循環(huán)中執(zhí)行蛉顽,looper 在哪個(gè)線(xiàn)程蝗砾,那么就是在哪個(gè)線(xiàn)程運(yùn)行。所以線(xiàn)程切換就是這么做的携冤。

MessageQueue 的 next() 方法內(nèi)部會(huì)調(diào)用 nativePollOnce() 方法悼粮,該方法會(huì)阻塞線(xiàn)程。該方法的作用簡(jiǎn)單說(shuō)曾棕,就是當(dāng)消息隊(duì)列中沒(méi)消息時(shí)扣猫,阻塞掉當(dāng)前執(zhí)行的線(xiàn)程.避免過(guò)度的cpu消耗。

handle.post 干啥了


為什么特別說(shuō)下 handle 的 post 方法睁蕾,因?yàn)橛械娜嗣嬖嚂?huì)問(wèn)苞笨,其實(shí)這個(gè)很簡(jiǎn)單,post 方法會(huì)生成一個(gè) message 對(duì)象子眶,然后把我們 post 方法里面的 runnable 對(duì)象存到 message 的 callback 里面去瀑凝,message 在執(zhí)行時(shí),會(huì)判斷有沒(méi)有 callback 值臭杰,有的話(huà)直接執(zhí)行消費(fèi)本地信息了粤咪,沒(méi)有的話(huà)才會(huì)把這個(gè) message 交給 handle 去執(zhí)行。

private static Message getPostMessage(Runnable r) {
                        // 1. 創(chuàng)建1個(gè)消息對(duì)象(Message)
                        Message m = Message.obtain();
                            // 注:創(chuàng)建Message對(duì)象可用關(guān)鍵字new 或 Message.obtain()
                            // 建議:使用Message.obtain()創(chuàng)建渴杆,
                            // 原因:因?yàn)镸essage內(nèi)部維護(hù)了1個(gè)Message池寥枝,用于Message的復(fù)用,使用obtain()直接從池內(nèi)獲取磁奖,從而避免使用new重新分配內(nèi)存

                        // 2. 將 Runable對(duì)象 賦值給消息對(duì)象(message)的callback屬性
                        m.callback = r;
                        
                        // 3. 返回該消息對(duì)象
                        return m;
                    } // 回到調(diào)用原處

對(duì)于 looper 的阻塞測(cè)試


其實(shí)看了上面對(duì)于 looper 阻塞的解釋后囊拜,我被這個(gè) Pipe 管道 hold 住了,原來(lái)用到了這么高大上的技術(shù)啊比搭,此時(shí)我非常覺(jué)得我得做點(diǎn)什么才行冠跷,于是不管怎樣樣總得干點(diǎn),干脆咱來(lái)看看這個(gè)阻塞是不是真的身诺,我是不是有點(diǎn)失心瘋啊...........

所以我做了個(gè)測(cè)試明確下思路:

  • 起2個(gè) thread 線(xiàn)程

  • thread1 里面跑一個(gè) looper 蜜托,然后把這個(gè) looper 拋出去,在 looper 啟動(dòng)之后加一個(gè) 打印循環(huán)

  • thread2 拿到 thread1 拋出的 looper 構(gòu)建 handle 發(fā)送消息

  • 最后看看這個(gè)在 looper 之后的打印循環(huán)有沒(méi)有執(zhí)行機(jī)會(huì)霉赡。

  • thread1

    public class Thread1 extends Thread {

        private Looper looper;

        @Override
        public void run() {
            super.run();

            Looper.prepare();
            looper = Looper.myLooper();
            Looper.loop();

            int num = 1;
            while (num <= 50) {
                Log.d("AAA", Thread.currentThread().getName() + "_執(zhí)行次數(shù) / " + num);
                num++;
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }

        public Looper getLooper() {
            return looper;
        }
    }
  • thread2
    public class Thread2 extends Thread {

        private Handler handler;

        public void setHandler(Handler handler) {
            this.handler = handler;
        }

        @Override
        public void run() {

            int num = 1;

            while (num <= 3) {
                try {
                    java.lang.Thread.sleep(1000);
                    if (handler != null) {
                        Message message = new Message();
                        message.what = 2;
                        handler.sendMessage(message);
                        Log.d("AAA", Thread.currentThread().getName() + "發(fā)送消息 / " + num);
                    }
                    num++;

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

  • UI 線(xiàn)程啟動(dòng)這 2 個(gè)測(cè)試線(xiàn)程
        Thread1 t1 = new Thread1();
        Thread2 t2 = new Thread2();

        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Handler handler = new Handler(t1.getLooper()) {

            int num = 0;

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.what == 2) {
                    num++;
                    Log.d("AAA", Thread.currentThread().getName() + "接受到消息 / " + num);
                }
            }
        };
        t2.setHandler(handler);

        t2.start();

    }

UI 線(xiàn)程中中間 sleep 1秒橄务,因?yàn)?looper 初始化需要一點(diǎn)時(shí)間,handle 中傳入的 looper 要是 null 的話(huà)會(huì)拋異常穴亏。

測(cè)試結(jié)果

測(cè)試結(jié)果是果然 thread1 后面的打印循環(huán)是出不來(lái)的蜂挪,looper 的確是阻塞了當(dāng)前線(xiàn)程的重挑,looper 之后的代碼都是沒(méi)機(jī)會(huì)執(zhí)行的

  • 首先 looper 里面的隊(duì)列是無(wú)限循環(huán)的,這個(gè)循環(huán)出不去后面的代碼肯定不會(huì)執(zhí)行
  • 再次 looper 的阻塞會(huì)阻塞這個(gè)無(wú)限循環(huán)棠涮,進(jìn)而阻塞當(dāng)前線(xiàn)程攒驰。

我想說(shuō)這個(gè)測(cè)試有點(diǎn)湊字?jǐn)?shù)的嫌疑,但是我的確在 looper 無(wú)限循環(huán)故爵,阻塞,喚醒的問(wèn)題上找了好多資料隅津,折騰了好幾回诬垂,非常不爽啊

ThreadLocal


ThreadLocal 是一個(gè)存儲(chǔ)器,作用域在單個(gè)線(xiàn)程對(duì)象內(nèi)部伦仍,多線(xiàn)程間是無(wú)法共享的结窘,相當(dāng)于線(xiàn)程對(duì)象私有的變量存儲(chǔ)器。這個(gè)存儲(chǔ)器只能存一個(gè)數(shù)據(jù)充蓝,那么要是想存多個(gè)數(shù)據(jù)的話(huà)隧枫,就需要多個(gè) ThreadLocal 對(duì)象了。

  • threadLocal.set("BB") -- > 存數(shù)據(jù)
  • threadLocal.get() -- > 取數(shù)據(jù)
    通過(guò)這 2 個(gè)方法就知道了吧谓苟,只能存一個(gè)數(shù)據(jù)進(jìn)去

一般我們認(rèn)為官脓,你在一個(gè)類(lèi)里面聲明的一個(gè)對(duì)象,這個(gè)對(duì)象的作用域肯定就是你這個(gè)類(lèi)的對(duì)象啊涝焙。但是注意 ThreadLocal 的特別之處在于即使你在3個(gè)線(xiàn)程之內(nèi)卑笨,操作同一個(gè)ThreadLocal 對(duì)象里面的值,那么你會(huì)發(fā)現(xiàn) 3個(gè)線(xiàn)程所屬的 ThreadLocal 里面對(duì)應(yīng)的值都是不同的仑撞。

下面是一個(gè)測(cè)試赤兴,在 UI 線(xiàn)程啟動(dòng)2個(gè) thread ,這3個(gè)線(xiàn)程一齊修改同一個(gè) ThreadLocal 里面的數(shù)據(jù)

        ThreadLocal threadLocal = new ThreadLocal();
        threadLocal.set("AA");

        Thread t1 = new Thread() {

            @Override
            public void run() {
                super.run();
                threadLocal.set("BB");
                Log.d("AA", Thread.currentThread().getName() + " / ThreadLocal : value = " + threadLocal.get());
            }
        };

        Thread t2 = new Thread() {

            @Override
            public void run() {
                super.run();
                threadLocal.set("CC");
                Log.d("AA", Thread.currentThread().getName() + " / ThreadLocal : value = " + threadLocal.get());
            }
        };

        t1.start();
        t2.start();

        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.d("AA", Thread.currentThread().getName() + " / ThreadLocal : value = " + threadLocal.get());

    }
測(cè)試結(jié)果

ThreadLocal 的使用場(chǎng)景并不多隧哮,基本都是用這個(gè)作用域范圍桶良,像 Looper,ActivityThread沮翔,AMS 這些陨帆。

趴趴源碼實(shí)現(xiàn)

為啥 ThreadLocal 這么屌呢,其實(shí)也沒(méi)啥鉴竭,主要我們嘗試看源碼歧譬,就會(huì)發(fā)現(xiàn)很多東西其實(shí)也是很簡(jiǎn)單的,就是設(shè)計(jì)很靈活搏存,很 Nice

  1. looper 對(duì)象里有一個(gè) static 的 ThreadLocal 對(duì)象瑰步,聲明對(duì)象時(shí)就初始化了
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
  1. ThreadLocal 的 ge() 干了什么
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

獲取當(dāng)前線(xiàn)程對(duì)象,然后從拿到線(xiàn)程對(duì)象里面的參數(shù) ThreadLocalMap 璧眠,ThreadLocal 的 map 集合缩焦,然后以 ThreadLocal 對(duì)象自己為 key 獲取到 value 值读虏,也就是我們儲(chǔ)存的數(shù)據(jù)

  1. 我們來(lái)看看這個(gè)map 里面的 Entry 的聲明
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

很明顯的可以看到 key 是 ThreadLocal 對(duì)象本身,value 是我們儲(chǔ)存的值袁滥。每個(gè) thread 對(duì)象內(nèi)部都有一個(gè) map 集合的話(huà)盖桥,通過(guò)同一個(gè) key 我們當(dāng)然會(huì)可以存不同的值進(jìn)去了。

其他人的解釋

ThreadLocal是一種保存變量的線(xiàn)程安全的類(lèi).
在多線(xiàn)程環(huán)境下能安全使用變量,最好的方式是在每個(gè)線(xiàn)程中定義一個(gè)local變量.
這樣對(duì)線(xiàn)程中的local變量做修改就只會(huì)影響當(dāng)前線(xiàn)程中的該變量,因此變量也就是線(xiàn)程安全的.
這種保證變量線(xiàn)程安全的思想,實(shí)際上就是ThreadLocal的實(shí)現(xiàn).

Threadlocal在調(diào)用threadLocal.get方法時(shí),會(huì)獲取當(dāng)前thread的threadLocalMap.
threadLocalMap是thread中的一個(gè)屬性.
第一次調(diào)用ThreadLocal.get()方法時(shí),會(huì)先判斷當(dāng)前線(xiàn)程對(duì)應(yīng)的threadlocalMap是否被創(chuàng)建了,
如果沒(méi)創(chuàng)建則會(huì)創(chuàng)建ThreadLocalMap,并把該對(duì)象賦值給thread.sThreadLocal對(duì)象.后續(xù)再獲取當(dāng)前thread的threadLocalMap時(shí),就會(huì)取該賦值對(duì)象.
ThreadLocalMap就是用來(lái)保存線(xiàn)程中需要保存的變量的對(duì)象了.

因?yàn)閠hreadLocalMap是賦值給當(dāng)前thread的,屬于thread的內(nèi)部變量,
所以每個(gè)線(xiàn)程的threadlocalMap就都是不同的對(duì)象,也就是上面說(shuō)的threadlocal是線(xiàn)程安全的原因了.

ThreadLocalMap內(nèi)部實(shí)際上是一個(gè)Entry[],用來(lái)保存Entry對(duì)象的數(shù)組.
Entry對(duì)象是繼承weakReference的,其中Entry的key為T(mén)hreadLocal對(duì)象,value為threadLocal需要保存的變量值.

調(diào)用ThreadLocal.set方法時(shí),會(huì)向threadLocalMap中添加一個(gè)Entry對(duì)象.
調(diào)用get方法時(shí),是通過(guò)將調(diào)用的threadLocal對(duì)象本身作為key,來(lái)遍歷threadLocalMap數(shù)組.
當(dāng)threadLocal等于Entry[]中的key時(shí),則返回該Entry中的value

為什么主線(xiàn)程不會(huì)因?yàn)?Looper.loop() 的死循環(huán)卡死


這是個(gè)經(jīng)典的問(wèn)題了题翻,我們先來(lái)看看主線(xiàn)程啟動(dòng)時(shí)干了什么

public static void main(String[] args) {
        ....

        //創(chuàng)建Looper和MessageQueue對(duì)象揩徊,用于處理主線(xiàn)程的消息
        Looper.prepareMainLooper();

        //創(chuàng)建ActivityThread對(duì)象
        ActivityThread thread = new ActivityThread(); 

        //建立Binder通道 (創(chuàng)建新線(xiàn)程)
        thread.attach(false);

        Looper.loop(); //消息循環(huán)運(yùn)行
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

核心就是給主線(xiàn)程添加了 looper 進(jìn)去,然后啟動(dòng) looper 這個(gè)隊(duì)列嵌赠,looper 實(shí)際是一個(gè)阻塞是死循環(huán)塑荒,簡(jiǎn)單介紹一下,有消息就處理姜挺,沒(méi)有消息就會(huì)休眠齿税,有消息就會(huì)喚醒繼續(xù)處理消息,本質(zhì)上是一套完整的線(xiàn)程通信機(jī)制

    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            try {
                msg.target.dispatchMessage(msg);
            }
        }
    }

看到 loop 這個(gè)方法了沒(méi)炊豪, for (;;) 就是個(gè) while(true) 死循環(huán)凌箕,queue.next() 獲取隊(duì)列的消息,隊(duì)列為空的時(shí)候會(huì)阻塞主線(xiàn)程词渤,這里就是關(guān)鍵了牵舱,queue.next() 的阻塞是不會(huì)造成 ANR 的。

先說(shuō)一點(diǎn)缺虐,為什么主線(xiàn)程是個(gè)死循環(huán)還要要阻塞仆葡,淡村的死循環(huán)空閑時(shí)也不阻塞,會(huì)消耗大量的 CPU 資源志笼,這是非常不值得的操作沿盅,所以在空閑時(shí)要阻塞

java 的阻塞分兩種,阻塞失去鎖纫溃,阻塞不失去鎖腰涧,不丟失鎖的這種阻塞也叫休眠,隨時(shí)可以喚醒紊浩,喚醒就能馬上執(zhí)行任務(wù)窖铡。wait() 是失去鎖的阻塞,sleep() 就是不失去鎖的阻塞也就是休眠了坊谁,只不過(guò) loop 這里的休眠式阻塞是交給 linux 層去實(shí)現(xiàn)的

在添加任務(wù)到隊(duì)列的時(shí)候會(huì)用 linux 的方式 nativeWake 喚醒主線(xiàn)程费彼,nativeWake 是本地 C 的方法

boolean enqueueMessage(Message msg, long when) {
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

private native static void nativeWake(long ptr);

在獲取任務(wù)時(shí)先用 linux 的方式 nativePollOnce 阻塞主線(xiàn)程,nativePollOnce 是本地 C 的方法

    Message next() {
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                    .......
                }
    }

    private native void nativePollOnce(long ptr, int timeoutMillis); 

這里就涉及到Linux pipe/e****poll機(jī)制口芍,簡(jiǎn)單說(shuō)就是在主線(xiàn)程的MessageQueue沒(méi)有消息時(shí)箍铲,便阻塞在loop的queue.next()中的nativePollOnce()方法里,詳情見(jiàn)Android消息機(jī)制1-Handler(Java層)鬓椭,此時(shí)主線(xiàn)程會(huì)釋放CPU資源進(jìn)入休眠狀態(tài)颠猴,直到下個(gè)消息到達(dá)或者有事務(wù)發(fā)生关划,通過(guò)往pipe管道寫(xiě)端寫(xiě)入數(shù)據(jù)來(lái)喚醒主線(xiàn)程工作悄蕾。這里采用的epoll機(jī)制片任,是一種IO多路復(fù)用機(jī)制,可以同時(shí)監(jiān)控多個(gè)描述符拳亿,當(dāng)某個(gè)描述符就緒(讀或?qū)懢途w)资盅,則立刻通知相應(yīng)程序進(jìn)行讀或?qū)懖僮鞯鏖举|(zhì)同步I/O,即讀寫(xiě)是阻塞的呵扛。 所以說(shuō)振峻,主線(xiàn)程大多數(shù)時(shí)候都是處于休眠狀態(tài),并不會(huì)消耗大量CPU資源择份。


thread.attach(false);便會(huì)創(chuàng)建一個(gè)Binder線(xiàn)程(具體是指ApplicationThread烫堤,Binder的服務(wù)端荣赶,用于接收系統(tǒng)服務(wù)AMS發(fā)送來(lái)的事件),該Binder線(xiàn)程通過(guò)Handler將Message發(fā)送給主線(xiàn)程**鸽斟,具體過(guò)程可查看 startService流程分析拔创,這里不展開(kāi)說(shuō),簡(jiǎn)單說(shuō)Binder用于進(jìn)程間通信富蓄,采用C/S架構(gòu)剩燥。關(guān)于binder感興趣的朋友,可查看
為什么Android要采用Binder作為IPC機(jī)制立倍? - Gityuan的回答

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末灭红,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子口注,更是在濱河造成了極大的恐慌变擒,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寝志,死亡現(xiàn)場(chǎng)離奇詭異娇斑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)材部,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)毫缆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人乐导,你說(shuō)我怎么就攤上這事苦丁。” “怎么了物臂?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵芬骄,是天一觀(guān)的道長(zhǎng)猾愿。 經(jīng)常有香客問(wèn)我,道長(zhǎng)账阻,這世上最難降的妖魔是什么蒂秘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮淘太,結(jié)果婚禮上姻僧,老公的妹妹穿的比我還像新娘。我一直安慰自己蒲牧,他們只是感情好撇贺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著冰抢,像睡著了一般松嘶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挎扰,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天翠订,我揣著相機(jī)與錄音,去河邊找鬼遵倦。 笑死尽超,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的梧躺。 我是一名探鬼主播似谁,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼掠哥!你這毒婦竟也來(lái)了巩踏?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤续搀,失蹤者是張志新(化名)和其女友劉穎蛀缝,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體目代,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡屈梁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了榛了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片在讶。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖霜大,靈堂內(nèi)的尸體忽然破棺而出构哺,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布曙强,位于F島的核電站残拐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏碟嘴。R本人自食惡果不足惜溪食,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望娜扇。 院中可真熱鬧错沃,春花似錦、人聲如沸雀瓢。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)刃麸。三九已至醒叁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間泊业,已是汗流浹背把沼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脱吱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓认罩,卻偏偏與公主長(zhǎng)得像箱蝠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子垦垂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355