ThreadLocal小結(jié)-到底會(huì)不會(huì)引起內(nèi)存泄露

[TOC]

1. ThreadLocal簡介

網(wǎng)上看到一些文章,提到關(guān)于ThreadLocal可能引起的內(nèi)存泄露派敷,搞得都不敢在代碼里隨意使用了所刀,于是來研究下,看看到底ThreadLocal會(huì)不會(huì)導(dǎo)致內(nèi)存泄露瓶埋,什么情況下會(huì)導(dǎo)致泄露希柿。

ThreadLocal,顧名思義养筒,其存儲的內(nèi)容是線程私本地的/私有的曾撤,我們常使用ThreadLocal來存儲/維護(hù)一些資源或者變量,以避免線程爭用或者同步問題晕粪,例如使用ThreadLocal來為每個(gè)線程維持一個(gè)redis連接(生產(chǎn)中這也許不是一個(gè)好的方式挤悉,還是推薦專業(yè)的連接池)或者維持一些線程私有的變量等。

例如巫湘,假設(shè)我們在一個(gè)線程應(yīng)用中需要對時(shí)間做格式化装悲,我們很容易想到的是使用SimpleDateFormat這個(gè)工具類,但是SimpleDateFormat不是線程安全的尚氛,那么我們通常用兩種做法:

  • 每次用到的時(shí)候new一個(gè)SimpleDateFormat對象诀诊,使用完丟棄,交給gc
  • 每個(gè)線程維護(hù)一個(gè)SimpleDateFormat實(shí)例阅嘶,線程運(yùn)行期間不重復(fù)創(chuàng)建

那么無論從執(zhí)行效率還是內(nèi)存占用方面属瓣,我們都傾向于使用后者,即線程私有一個(gè)SimpleDateFormat對象,這時(shí)候奠涌,ThreadLocal就是很好的應(yīng)用宪巨,示例代碼如下:

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestTask implements Runnable {
    private boolean stop = false;
    private ThreadLocal<SimpleDateFormat> sdfHolder = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyyMMdd");
        }
    };

    @Override
    public void run() {
        while (!stop) {
            String formatedDateStr = sdfHolder.get().format(new Date());
            System.out.println("formated date str:" + formatedDateStr);
        //may be sleep for a while to avoid high cpu cost
        }
        sdfHolder.remove();
    }
    
    //something else
}

代碼中模擬了一個(gè)需要反復(fù)執(zhí)行的Task,其run方法中溜畅,while條件除非stop是true捏卓,否則就一直運(yùn)轉(zhuǎn)下去。在該示例中通過ThreadLocal為每個(gè)線程實(shí)例化了一個(gè)SimpleDateFormat對象慈格,當(dāng)需要的時(shí)候怠晴,通過get()獲取即可,實(shí)現(xiàn)了每個(gè)線程全程只有一個(gè)SimpleDateFormat對象浴捆。同時(shí)在stop為true時(shí)使用ThreadLocal的remove方法刪除當(dāng)前線程使用的SimpleDateFormat對象蒜田,以便于垃圾回收。

僅演示ThreadLocal用法选泻,暫不討論代碼設(shè)計(jì)

2. ThreadLocal內(nèi)存模型

上面我們簡單介紹了ThreadLocal的概念和使用冲粤,下面看下ThreadLocal的內(nèi)存模型。

2.1 ThreadLocal內(nèi)存模型

2.1.1 私有變量存儲在哪里

在代碼中页眯,我們使用ThreadLocal實(shí)例提供的set/get方法來存儲/使用value梯捕,但ThreadLocal實(shí)例其實(shí)只是一個(gè)引用,真正存儲值的是一個(gè)Map窝撵,其key實(shí)ThreadLocal實(shí)例本身傀顾,value是我們設(shè)置的值,分布在堆區(qū)碌奉。這個(gè)Map的類型是ThreadL.ThreadLocalMap(ThreadLocalMap是ThreadLocal的內(nèi)部類)短曾,其key的類型是ThreadLocal,value是Object赐劣,類定義如下:

    static class ThreadLocalMap {
        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

那么當(dāng)我們重寫init或者調(diào)用set/get的時(shí)候嫉拐,內(nèi)部的邏輯是怎樣的呢,按照上面的說法隆豹,應(yīng)該是將value存儲到了ThreadLocalMap中椭岩,或者從已有的ThreadLocalMap中獲取value茅逮,我們來通過代碼分析一下璃赡。

ThreadLocal.set(T value)

set的邏輯比較簡單,就是獲取當(dāng)前線程的ThreadLocalMap献雅,然后往map里添加KV碉考,K是this,也就是當(dāng)前ThreadLocal實(shí)例挺身,V是我們傳入的value侯谁。

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

其內(nèi)部實(shí)現(xiàn)首先需要獲取關(guān)聯(lián)的Map,我們看下getMap和createMap的實(shí)現(xiàn)

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     * @param map the map to store.
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

可以看到,getMap就是返回了當(dāng)前Thread實(shí)例的map(t.threadLocals)墙贱,create也是創(chuàng)建了Thread的map(t.threadLocals)热芹,也就是說對于一個(gè)Thread實(shí)例,ThreadLocalMap是其內(nèi)部的一個(gè)屬性惨撇,在需要的時(shí)候伊脓,可以通過ThreadLocal創(chuàng)建或者獲取,然后存放相應(yīng)的值魁衙。我們看下Thread類的關(guān)鍵代碼

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    //省略了其他代碼
}

可以看到报腔,Thread中定義了屬性threadLocals,但其初始化和使用的過程剖淀,都是通過ThreadLocal這個(gè)類來執(zhí)行的纯蛾。

ThreadLocal.get()

get是獲取當(dāng)前線程的對應(yīng)的私有變量,是我們之前set或者通過initialValue指定的變量纵隔,其代碼如下

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

可以看到翻诉,其邏輯也比較簡單清晰:

  • 獲取當(dāng)前線程的ThreadLocalMap實(shí)例
  • 如果不為空,以當(dāng)前ThreadLocal實(shí)例為key獲取value
  • 如果ThreadLocalMap為空或者根據(jù)當(dāng)前ThreadLocal實(shí)例獲取的value為空捌刮,則執(zhí)行setInitialValue()

setInitialValue()內(nèi)部如下:

  • 調(diào)用我們重寫的initialValue得到一個(gè)value
  • 將value放入到當(dāng)前線程對應(yīng)的ThreadLocalMap中
  • 如果map為空米丘,先實(shí)例化一個(gè)map,然后賦值KV

關(guān)鍵設(shè)計(jì)小結(jié)

代碼分析到這里糊啡,其實(shí)對于ThreadLocal的內(nèi)部主要設(shè)計(jì)以及其和Thread的關(guān)系比較清楚了:

  • 每個(gè)線程拄查,是一個(gè)Thread實(shí)例,其內(nèi)部擁有一個(gè)名為threadLocals的實(shí)例成員棚蓄,其類型是ThreadLocal.ThreadLocalMap
  • 通過實(shí)例化ThreadLocal實(shí)例堕扶,我們可以對當(dāng)前運(yùn)行的線程設(shè)置一些線程私有的變量,通過調(diào)用ThreadLocal的set和get方法存取
  • ThreadLocal本身并不是一個(gè)容器梭依,我們存取的value實(shí)際上存儲在ThreadLocalMap中稍算,ThreadLocal只是作為TheadLocalMap的key
  • 每個(gè)線程實(shí)例都對應(yīng)一個(gè)TheadLocalMap實(shí)例,我們可以在同一個(gè)線程里實(shí)例化很多個(gè)ThreadLocal來存儲很多種類型的值役拴,這些ThreadLocal實(shí)例分別作為key糊探,對應(yīng)各自的value
  • 當(dāng)調(diào)用ThreadLocal的set/get進(jìn)行賦值/取值操作時(shí),首先獲取當(dāng)前線程的ThreadLocalMap實(shí)例河闰,然后就像操作一個(gè)普通的map一樣科平,進(jìn)行put和get

當(dāng)然,這個(gè)ThreadLocalMap并不是一個(gè)普通的Map(比如常用的HashMap)姜性,而是一個(gè)特殊的瞪慧,key為弱引用的map,這個(gè)我們后面再詳談

2.1.2 ThreadLocal內(nèi)存模型

通過上一節(jié)的分析部念,其實(shí)我們已經(jīng)很清楚ThreadLocal的相關(guān)設(shè)計(jì)了弃酌,對數(shù)據(jù)存儲的具體分布也會(huì)有個(gè)比較清晰的概念氨菇。下面的圖是網(wǎng)上找來的常見到的示意圖,我們可以通過該圖對ThreadLocal的存儲有個(gè)更加直接的印象妓湘。

TheadLocal內(nèi)存模型

我們知道Thread運(yùn)行時(shí)查蓉,線程的的一些局部變量和引用使用的內(nèi)存屬于Stack(棧)區(qū),而普通的對象是存儲在Heap(堆)區(qū)榜贴。根據(jù)上圖奶是,基本分析如下:

  • 線程運(yùn)行時(shí),我們定義的TheadLocal對象被初始化竣灌,存儲在Heap聂沙,同時(shí)線程運(yùn)行的棧區(qū)保存了指向該實(shí)例的引用,也就是圖中的ThreadLocalRef
  • 當(dāng)ThreadLocal的set/get被調(diào)用時(shí)初嘹,虛擬機(jī)會(huì)根據(jù)當(dāng)前線程的引用也就是CurrentThreadRef找到其對應(yīng)在堆區(qū)的實(shí)例及汉,然后查看其對用的TheadLocalMap實(shí)例是否被創(chuàng)建,如果沒有屯烦,則創(chuàng)建并初始化坷随。
  • Map實(shí)例化之后,也就拿到了該ThreadLocalMap的句柄驻龟,然后如果將當(dāng)前ThreadLocal對象作為key温眉,進(jìn)行存取操作
  • 圖中的虛線,表示key對ThreadLocal實(shí)例的引用是個(gè)弱引用

3. 插曲:強(qiáng)引用/弱引用

java中的引用分為四種翁狐,按照引用強(qiáng)度不同类溢,從強(qiáng)到弱依次為:強(qiáng)引用、軟引用露懒、弱引用和虛引用闯冷,如果不是專門做jvm研究,對其概念很難清晰的定義懈词,我們大致可以理解為蛇耀,引用的強(qiáng)度,代表了對內(nèi)存占用的能力大小坎弯,具體體現(xiàn)在GC的時(shí)候纺涤,會(huì)不會(huì)被回收,什么時(shí)候被回收抠忘。

ThreadLocal被用作TheadLocalMap的弱引用key撩炊,這種設(shè)計(jì)也是ThreadLocal被討論內(nèi)存泄露的熱點(diǎn)問題,因此有必要了解一下什么是弱引用褐桌。

3.1 強(qiáng)引用

強(qiáng)引用雖然在開發(fā)過程中并不怎么提及衰抑,但是無處不在,例如我們在一個(gè)對象中通過如下代碼實(shí)例化一個(gè)StringBuffer對象

StringBuffer buffer = new StringBuffer();

我們知道StringBuffer的實(shí)例通常是被創(chuàng)建在堆中的荧嵌,而當(dāng)前對象持有該StringBuffer對象的引用呛踊,以便后續(xù)的訪問,這個(gè)引用啦撮,就是一個(gè)強(qiáng)引用谭网。

對GC知識比較熟悉的可以知道,HotSpot JVM目前的垃圾回收算法一般默認(rèn)是可達(dá)性算法赃春,即在每一輪GC的時(shí)候愉择,選定一些對象作為GC ROOT,然后以它們?yōu)楦l(fā)散遍歷织中,遍歷完成之后锥涕,如果一個(gè)對象不被任何GC ROOT引用,那么它就是不可達(dá)對象狭吼,則在接下來的GC過程中很可能會(huì)被回收层坠。

強(qiáng)引用最重要的就是它能夠讓引用變得強(qiáng)(Strong),這就決定了它和垃圾回收器的交互刁笙。具體來說破花,如果一個(gè)對象通過一串強(qiáng)引用鏈接可到達(dá)(Strongly reachable),它是不會(huì)被回收的疲吸。如果你不想讓你正在使用的對象被回收座每,這就正是你所需要的。

3.2 軟引用

軟引用是用來描述一些還有用但是并非必須的對象摘悴。對于軟引用關(guān)聯(lián)著的對象峭梳,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對象列進(jìn)回收返回之后進(jìn)行第二次回收蹂喻。如果這次回收還沒有足夠的內(nèi)存延赌,才會(huì)拋出內(nèi)存溢出異常。JDK1.2之后提供了SoftReference來實(shí)現(xiàn)軟引用叉橱。

相對于強(qiáng)引用挫以,軟引用在內(nèi)存充足時(shí)可能不會(huì)被回收,在內(nèi)存不夠時(shí)會(huì)被回收窃祝。

3.3 弱引用

弱引用也是用來描述非必須的對象的掐松,但它的強(qiáng)度更弱,被弱引用關(guān)聯(lián)的對象只能生存到下一次GC發(fā)生之前粪小,也就是說下一次GC就會(huì)被回收大磺。JDK1.2之后,提供了WeakReference來實(shí)現(xiàn)弱引用探膊。

3.4 虛引用

虛引用也成為幽靈引用或者幻影引用杠愧,它是最弱的一種引用關(guān)系。一個(gè)瑞祥是否有虛引用的存在逞壁,完全不會(huì)對其生存時(shí)間造成影響流济,也無法通過虛引用來取得一個(gè)對象的實(shí)例锐锣。為一個(gè)對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是在這個(gè)對象被GC時(shí)收到一個(gè)系統(tǒng)通知。JDK1.2之后提供了PhantomReference來實(shí)現(xiàn)虛引用

4. 可能的內(nèi)存泄露分析

了解了ThreadLocal的內(nèi)部模型以及弱引用绳瘟,接下來可以分析一下是否有內(nèi)存泄露的可能以及如何避免雕憔。

4.1 內(nèi)存泄露分析

根據(jù)上一節(jié)的內(nèi)存模型圖我們可以知道,由于ThreadLocalMap是以弱引用的方式引用著ThreadLocal糖声,換句話說斤彼,就是ThreadLocal是被ThreadLocalMap以弱引用的方式關(guān)聯(lián)著,因此如果ThreadLocal沒有被ThreadLocalMap以外的對象引用蘸泻,則在下一次GC的時(shí)候琉苇,ThreadLocal實(shí)例就會(huì)被回收,那么此時(shí)ThreadLocalMap里的一組KV的K就是null了悦施,因此在沒有額外操作的情況下并扇,此處的V便不會(huì)被外部訪問到,而且只要Thread實(shí)例一直存在歼争,Thread實(shí)例就強(qiáng)引用著ThreadLocalMap拜马,因此ThreadLocalMap就不會(huì)被回收,那么這里K為null的V就一直占用著內(nèi)存沐绒。

綜上俩莽,發(fā)生內(nèi)存泄露的條件是

  • ThreadLocal實(shí)例沒有被外部強(qiáng)引用,比如我們假設(shè)在提交到線程池的task中實(shí)例化的ThreadLocal對象乔遮,當(dāng)task結(jié)束時(shí)扮超,ThreadLocal的強(qiáng)引用也就結(jié)束了
  • ThreadLocal實(shí)例被回收,但是在ThreadLocalMap中的V沒有被任何清理機(jī)制有效清理
  • 當(dāng)前Thread實(shí)例一直存在蹋肮,則會(huì)一直強(qiáng)引用著ThreadLocalMap出刷,也就是說ThreadLocalMap也不會(huì)被GC

也就是說,如果Thread實(shí)例還在,但是ThreadLocal實(shí)例卻不在了,則ThreadLocal實(shí)例作為key所關(guān)聯(lián)的value無法被外部訪問俭令,卻還被強(qiáng)引用著,因此出現(xiàn)了內(nèi)存泄露坷檩。

也就是說,我們回答了文章開頭的第一個(gè)問題改抡,ThreadLocal如果使用的不當(dāng)矢炼,是有可能引起內(nèi)存泄露的,雖然觸發(fā)的場景不算很容易阿纤。

這里要額外說明一下句灌,這里說的內(nèi)存泄露,是因?yàn)閷ζ鋬?nèi)存模型和設(shè)計(jì)不了解欠拾,且編碼時(shí)不注意導(dǎo)致的內(nèi)存管理失聯(lián)胰锌,而不是有意為之的一直強(qiáng)引用或者頻繁申請大內(nèi)存骗绕。比如如果編碼時(shí)不停的人為塞一些很大的對象,而且一直持有引用最終導(dǎo)致OOM匕荸,不能算作ThreadLocal導(dǎo)致的“內(nèi)存泄露”爹谭,只是代碼寫的不當(dāng)而已枷邪!

4.2 TheadLocal本身的優(yōu)化

進(jìn)一步分析ThreadLocalMap的代碼榛搔,可以發(fā)現(xiàn)ThreadLocalMap內(nèi)部也是做了一定的優(yōu)化的

        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

可以看到,在set值的時(shí)候东揣,有一定的幾率會(huì)執(zhí)行replaceStaleEntry(key, value, i)方法践惑,其作用就是將當(dāng)前的值替換掉以前的key為null的值,重復(fù)利用了空間嘶卧。

5. ThreadLocal使用建議

通過前面幾節(jié)的分析尔觉,我們基本弄清楚了ThreadLocal相關(guān)設(shè)計(jì)和內(nèi)存模型,對于是否會(huì)發(fā)生內(nèi)存泄露做了分析芥吟,下面總結(jié)下幾點(diǎn)建議:

  • 當(dāng)需要存儲線程私有變量的時(shí)候侦铜,可以考慮使用ThreadLocal來實(shí)現(xiàn)
  • 當(dāng)需要實(shí)現(xiàn)線程安全的變量時(shí),可以考慮使用ThreadLocal來實(shí)現(xiàn)
  • 當(dāng)需要減少線程資源競爭的時(shí)候钟鸵,可以考慮使用ThreadLocal來實(shí)現(xiàn)
  • 注意Thread實(shí)例和ThreadLocal實(shí)例的生存周期钉稍,因?yàn)樗麄冎苯雨P(guān)聯(lián)著存儲數(shù)據(jù)的生命周期
    • 如果頻繁的在線程中new ThreadLocal對象,在使用結(jié)束時(shí)棺耍,最好調(diào)用ThreadLocal.remove來釋放其value的引用贡未,避免在ThreadLocal被回收時(shí)value無法被訪問卻又占用著內(nèi)存

其實(shí)對于ThreadLocalMap還有很多設(shè)計(jì),關(guān)于其詳細(xì)內(nèi)容蒙袍,可以參考文后參考文章的最后一篇

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俊卤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子害幅,更是在濱河造成了極大的恐慌消恍,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件以现,死亡現(xiàn)場離奇詭異狠怨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)叼风,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門取董,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人无宿,你說我怎么就攤上這事茵汰。” “怎么了孽鸡?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵蹂午,是天一觀的道長栏豺。 經(jīng)常有香客問我,道長豆胸,這世上最難降的妖魔是什么奥洼? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮晚胡,結(jié)果婚禮上灵奖,老公的妹妹穿的比我還像新娘。我一直安慰自己估盘,他們只是感情好瓷患,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著遣妥,像睡著了一般擅编。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上箫踩,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天爱态,我揣著相機(jī)與錄音,去河邊找鬼境钟。 笑死锦担,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吱韭。 我是一名探鬼主播吆豹,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼理盆!你這毒婦竟也來了痘煤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤猿规,失蹤者是張志新(化名)和其女友劉穎衷快,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姨俩,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蘸拔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了环葵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片调窍。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖张遭,靈堂內(nèi)的尸體忽然破棺而出邓萨,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布缔恳,位于F島的核電站宝剖,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏歉甚。R本人自食惡果不足惜万细,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纸泄。 院中可真熱鬧赖钞,春花似錦、人聲如沸刃滓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咧虎。三九已至,卻和暖如春计呈,著一層夾襖步出監(jiān)牢的瞬間砰诵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工捌显, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留茁彭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓扶歪,卻偏偏與公主長得像理肺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子善镰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

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