ThreadLocal

ThreadLocal 概述

概述

  • ThreadLocal類用來提供線程內(nèi)部的局部變量,不同的線程之間不會(huì)相互干擾
  • 這種變量在多線程環(huán)境下訪問(通過get和set方法訪問)時(shí)能保證各個(gè)線程的變量相對(duì)獨(dú)立于其他線程內(nèi)的變量
  • 在線程的生命周期內(nèi)起作用实苞,可以減少同一個(gè)線程內(nèi)多個(gè)函數(shù)或組件之間一些公共變量傳遞的復(fù)雜度

常用方法

方法名 描述
ThreadLocal() 創(chuàng)建ThreadLocal對(duì)象
public void set( T value) 設(shè)置當(dāng)前線程綁定的局部變量
public T get() 獲取當(dāng)前線程綁定的局部變量
public T remove() 移除當(dāng)前線程綁定的局部變量,該方法可以幫助JVM進(jìn)行GC
protected T initialValue() 返回當(dāng)前線程局部變量的初始值

為什么要用ThreadLocal?

但在并發(fā)的場(chǎng)景中丈莺,如果有多個(gè)線程同時(shí)修改公共變量瓮具,可能會(huì)出現(xiàn)線程安全問題定硝,即該變量最終結(jié)果可能出現(xiàn)異常晨雳。

為了解決線程安全問題,JDK出現(xiàn)了很多技術(shù)手段紧卒,比如:使用synchronized或Lock侥衬,給訪問公共資源的代碼上鎖,保證了代碼的原子性跑芳。

但在高并發(fā)的場(chǎng)景中轴总,如果多個(gè)線程同時(shí)競(jìng)爭(zhēng)一把鎖,這時(shí)會(huì)存在大量的鎖等待博个,可能會(huì)浪費(fèi)很多時(shí)間怀樟,讓系統(tǒng)的響應(yīng)時(shí)間一下子變慢。

因此盆佣,JDK還提供了另外一種用空間換時(shí)間的新思路:ThreadLocal往堡。

它的核心思想是:共享變量在每個(gè)線程都有一個(gè)副本,每個(gè)線程操作的都是自己的副本共耍,對(duì)另外的線程沒有影響虑灰。

ThreadLocal的原理是什么?

set(T value)和get()方法


    /**
     * 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() {
        //獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        //獲取當(dāng)前線程的成員變量ThreadLocalMap對(duì)象
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //根據(jù)threadLocal對(duì)象從map中獲取Entry對(duì)象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                //獲取保存的數(shù)據(jù)
                T result = (T)e.value;
                return result;
            }
        }
        //初始化數(shù)據(jù)
        return setInitialValue();
    }

    /**
     * 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) {
       //獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        //獲取當(dāng)前線程的成員變量ThreadLocalMap對(duì)象
        ThreadLocalMap map = getMap(t);
        //如果map不為空
        if (map != null)
            //將值設(shè)置到map中痹兜,key是this瘩缆,即threadLocal對(duì)象,value是傳入的value值
            map.set(this, value);
        else
           //如果map為空佃蚜,則需要?jiǎng)?chuàng)建新的map對(duì)象
            createMap(t, value);
    }
    
     /**
     * 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() {
        //獲取要初始化的數(shù)據(jù)
        T value = initialValue();
        //獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        //獲取當(dāng)前線程的成員變量ThreadLocalMap對(duì)象
        ThreadLocalMap map = getMap(t);
        //如果map不為空
        if (map != null)
            //將初始值設(shè)置到map中,key是this着绊,即threadLocal對(duì)象谐算,value是初始值
            map.set(this, value);
        else
           //如果map為空,則需要?jiǎng)?chuàng)建新的map對(duì)象
            createMap(t, value);
        return value;
    }
    
    
    /**
     * 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;
    }

ThreadLocal的get方法归露、set方法和setInitialValue方法洲脂,其實(shí)最終操作的都是ThreadLocalMap類中的數(shù)據(jù)。

每個(gè)線程中都有一個(gè)ThreadLocalMap數(shù)據(jù)結(jié)構(gòu),當(dāng)執(zhí)行set方法時(shí)恐锦,其值是保存在當(dāng)前線程的threadLocals變量中往果,當(dāng)執(zhí)行g(shù)et方法中,是從當(dāng)前線程的threadLocals變量獲取一铅。

ThreadLoalMap


    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
        ...
    }

ThreadLocalMap里面包含一個(gè)靜態(tài)的內(nèi)部類Entry陕贮,該類繼承于WeakReference類,說明Entry是一個(gè)弱引用潘飘。

在ThreadLoalMap中肮之,也是初始化一個(gè)大小16的Entry數(shù)組,Entry對(duì)象用來保存每一個(gè)key-value鍵值對(duì)卜录,只不過這里的key永遠(yuǎn)都是ThreadLocal對(duì)象戈擒,通過ThreadLocal對(duì)象的set方法,結(jié)果把ThreadLocal對(duì)象自己當(dāng)做key艰毒,放進(jìn)了ThreadLoalMap中筐高。


圖片.png

從上圖中看出,在每個(gè)Thread類中丑瞧,都有一個(gè)ThreadLocalMap的成員變量柑土,該變量包含了一個(gè)Entry數(shù)組,該數(shù)組真正保存了ThreadLocal類set的數(shù)據(jù)嗦篱。

public class Thread implements Runnable {
    ...
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

Entry是由threadLocal和value組成冰单,其中threadLocal對(duì)象是弱引用,在GC的時(shí)候灸促,會(huì)被自動(dòng)回收诫欠。而value就是ThreadLocal類set的數(shù)據(jù)。

這里需要注意的是浴栽,ThreadLoalMap的Entry是繼承WeakReference荒叼,和HashMap很大的區(qū)別是,Entry中沒有next字段典鸡,所以就不存在鏈表的情況了被廓。

為什么用ThreadLocal做key?

如果在你的應(yīng)用中萝玷,一個(gè)線程中只使用了一個(gè)ThreadLocal對(duì)象嫁乘,那么使用Thread做key也未嘗不可。

@Service
public class ThreadLocalService {
    private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
}  

但實(shí)際情況中球碉,你的應(yīng)用蜓斧,一個(gè)線程中很有可能不只使用了一個(gè)ThreadLocal對(duì)象。這時(shí)使用Thread做key不就有問題睁冬?

@Service
public class ThreadLocalService {
    private static final ThreadLocal<Integer> threadLocal1 = new ThreadLocal<>();
    private static final ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
}

一個(gè)使用類挎春,有兩個(gè)共享變量,也就是說用了兩個(gè)ThreadLocal成員變量的話。如果用線程id作為ThreadLocalMap的key直奋,怎么區(qū)分哪個(gè)ThreadLocal成員變量呢能庆?因此還是需要使用ThreadLocal作為Key來使用。每個(gè)ThreadLocal對(duì)象脚线,都可以由threadLocalHashCode屬性唯一區(qū)分的搁胆,每一個(gè)ThreadLocal對(duì)象都可以由這個(gè)對(duì)象的名字唯一區(qū)分。

圖片.png
圖片.png

Entry的key為什么設(shè)計(jì)成弱引用?

前面說過殉挽,Entry的key丰涉,傳入的是ThreadLocal對(duì)象,使用了WeakReference對(duì)象斯碌,即被設(shè)計(jì)成了弱引用一死。


圖片.png

那么,為什么要這樣設(shè)計(jì)呢傻唾?

我們先來回憶一下四種引用:

  • 強(qiáng)引用:我們平時(shí)new了一個(gè)對(duì)象就是強(qiáng)引用投慈,例如 Object obj = new Object();即使在內(nèi)存不足的情況下,JVM寧愿拋出OutOfMemory錯(cuò)誤也不會(huì)回收這種對(duì)象冠骄。

  • 軟引用:如果一個(gè)對(duì)象只具有軟引用伪煤,則內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它凛辣;如果內(nèi)存空間不足了抱既,就會(huì)回收這些對(duì)象的內(nèi)存。

  • 弱引用:具有弱引用的對(duì)象擁有更短暫的生命周期扁誓。如果一個(gè)對(duì)象只有弱引用存在了防泵,則下次GC將會(huì)回收掉該對(duì)象(不管當(dāng)前內(nèi)存空間足夠與否)。

  • 虛引用:如果一個(gè)對(duì)象僅持有虛引用蝗敢,那么它就和沒有任何引用一樣捷泞,在任何時(shí)候都可能被垃圾回收器回收。虛引用主要用來跟蹤對(duì)象被垃圾回收器回收的活動(dòng)寿谴。

假如key對(duì)ThreadLocal對(duì)象的弱引用锁右,改為強(qiáng)引用。

圖片.png

我們都知道ThreadLocal變量對(duì)ThreadLocal對(duì)象是有強(qiáng)引用存在的讶泰。

即使ThreadLocal變量生命周期完了咏瑟,設(shè)置成null了,但由于key對(duì)ThreadLocal對(duì)象還是強(qiáng)引用痪署。

就會(huì)存在這樣的強(qiáng)引用鏈:Thread變量 -> Thread對(duì)象 -> ThreadLocalMap -> Entry -> key -> ThreadLocal對(duì)象响蕴。

當(dāng)ThreadLocal的對(duì)象被回收了,但是ThreadLocalMap還持有ThreadLocal對(duì)象的強(qiáng)引用的話那么惠桃,ThreadLocal對(duì)象和ThreadLocalMap都將不會(huì)被GC回收,于是產(chǎn)生了內(nèi)存泄露問題。

如果key是弱引用辜王,當(dāng)ThreadLocal變量指向null之后劈狐,在GC做垃圾清理的時(shí)候,key會(huì)被自動(dòng)回收呐馆,其值也被設(shè)置成null肥缔。

圖片.png

如何避免內(nèi)存泄露?

圖片.png

ThreadLocal的get()、set()可能會(huì)清除ThreadLocalMap中key為null的Entry對(duì)象汹来,這樣對(duì)應(yīng)的value就沒有GC Roots可達(dá)了续膳,下次GC的時(shí)候就可以被回收,當(dāng)然如果調(diào)用remove方法收班,肯定會(huì)刪除對(duì)應(yīng)的Entry對(duì)象坟岔。

如果使用ThreadLocal的set方法之后,沒有顯示的調(diào)用remove方法摔桦,就有可能發(fā)生內(nèi)存泄露社付,所以養(yǎng)成良好的編程習(xí)慣十分重要,使用完ThreadLocal之后邻耕,記得調(diào)用remove方法鸥咖。

ThreadLocal<String> localName = new ThreadLocal();
try {
    localName.set("占小狼");
    // 其它業(yè)務(wù)邏輯
} finally {
    localName.remove();
}

假如ThreadLocalMap中存在很多key為null的Entry,但后面的程序兄世,一直都沒有調(diào)用過有效的ThreadLocal的get啼辣、set或remove方法。

那么御滩,Entry的value值一直都沒被清空鸥拧。

所以會(huì)存在這樣一條強(qiáng)引用鏈:Thread變量 -> Thread對(duì)象 -> ThreadLocalMap -> Entry -> value -> Object。

其結(jié)果就是:Entry和ThreadLocalMap將會(huì)長(zhǎng)期存在下去艾恼,會(huì)導(dǎo)致內(nèi)存泄露住涉。


圖片.png

實(shí)際上,我們的內(nèi)存泄漏的根本原因是钠绍,不再被使用的Entry舆声,沒有從線程的ThreadLocalMap中刪除。一般刪除不再使用的Entry有這兩種方式:

  • 一種就是柳爽,使用完ThreadLocal媳握,手動(dòng)調(diào)用remove(),把Entry從ThreadLocalMap中刪除

  • 另外一種方式就是:ThreadLocalMap的自動(dòng)清除機(jī)制去清除過期Entry.(ThreadLocalMap的get(),set()時(shí)都會(huì)觸發(fā)對(duì)過期Entry的清除)

ThreadLocal是如何解決hash沖突的呢磷脯?

我們看看getEntry是怎么做的:

 /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntry(ThreadLocal<?> key) {
            //通過hash算法獲取下標(biāo)值
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            //如果下標(biāo)位置上的key正好是我們所需要尋找的key
            if (e != null && e.get() == key)
               //說明找到數(shù)據(jù)了蛾找,直接返回
                return e;
            else
                //說明出現(xiàn)hash沖突了,繼續(xù)往后找
                return getEntryAfterMiss(key, i, e);
        }

再看看getEntryAfterMiss方法:


        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
           //判斷Entry對(duì)象如果不為空赵誓,則一直循環(huán)
            while (e != null) {
                ThreadLocal<?> k = e.get();
               //如果當(dāng)前Entry的key正好是我們所需要尋找的key
                if (k == key)
                   //說明這次真的找到數(shù)據(jù)了
                    return e;
                if (k == null)
                    //如果key為空打毛,則清理臟數(shù)據(jù)
                    expungeStaleEntry(i);
                else
                   //如果還是沒找到數(shù)據(jù)柿赊,則繼續(xù)往后找
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

關(guān)鍵看看nextIndex方法:

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

當(dāng)通過hash算法計(jì)算出的下標(biāo)小于數(shù)組大小,則將下標(biāo)值加1幻枉。否則碰声,即下標(biāo)大于等于數(shù)組大小,下標(biāo)變成0了熬甫。下標(biāo)變成0之后胰挑,則循環(huán)一次,下標(biāo)又變成1椿肩。瞻颂。。

ThreadLocal是如何定位數(shù)據(jù)的郑象?

在ThreadLocal的get贡这、set、remove方法中都有這樣一行代碼:

int i = key.threadLocalHashCode & (len-1);

通過key的hashCode值與數(shù)組的長(zhǎng)度減1扣唱。其中key就是ThreadLocal對(duì)象藕坯,與數(shù)組的長(zhǎng)度減1,相當(dāng)于除以數(shù)組的長(zhǎng)度減1噪沙,然后取模炼彪。

ThreadLocal從數(shù)組中找數(shù)據(jù)的過程大致是這樣的:

1.通過key的hashCode取余計(jì)算出一個(gè)下標(biāo)。

2.通過下標(biāo)正歼,在數(shù)組中定位具體Entry辐马,如果key正好是我們所需要的key,說明找到了局义,則直接返回?cái)?shù)據(jù)喜爷。

3.如果第2步?jīng)]有找到我們想要的數(shù)據(jù),則從數(shù)組的下標(biāo)位置萄唇,繼續(xù)往后面找檩帐。

4.如果第3步中找key的正好是我們所需要的key,說明找到了另萤,則直接返回?cái)?shù)據(jù)湃密。

5.如果還是沒有找到數(shù)據(jù),再繼續(xù)往后面找四敞。如果找到最后一個(gè)位置泛源,還是沒有找到數(shù)據(jù),則再?gòu)念^忿危,即下標(biāo)為0的位置达箍,繼續(xù)從前往后找數(shù)據(jù)。

6.直到找到第一個(gè)Entry為空為止铺厨。

父子線程如何共享數(shù)據(jù)缎玫?

在Thread類中除了成員變量threadLocals之外硬纤,還有另一個(gè)成員變量:inheritableThreadLocals

 /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

Thread類中的init方法


    /**
     * Initializes a Thread.
     *
     * @param g the Thread group
     * @param target the object whose run() method gets called
     * @param name the name of the new Thread
     * @param stackSize the desired stack size for the new thread, or
     *        zero to indicate that this parameter is to be ignored.
     * @param acc the AccessControlContext to inherit, or
     *            AccessController.getContext() if null
     * @param inheritThreadLocals if {@code true}, inherit initial values for
     *            inheritable thread-locals from the constructing thread
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        ...
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }



    /**
     * Factory method to create map of inherited thread locals.
     * Designed to be called only from Thread constructor.
     *
     * @param  parentMap the map associated with parent thread
     * @return a map containing the parent's inheritable bindings
     */
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
    }

可以發(fā)現(xiàn)碘梢,當(dāng)parent的inheritableThreadLocals不為null時(shí)咬摇,就會(huì)將parent的inheritableThreadLocals,賦值給前線程的inheritableThreadLocals煞躬。說白了,就是如果當(dāng)前線程的inheritableThreadLocals不為null逸邦,就從父線程哪里拷貝過來一個(gè)過來恩沛,類似于另外一個(gè)ThreadLocal,數(shù)據(jù)從父線程那里來的缕减。

public class ThreadLocalTest {

    public static void main(String[] args) {
        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
        threadLocal.set(6);
        System.out.println("父線程獲取數(shù)據(jù):" + threadLocal.get());

        new Thread(() -> {
            System.out.println("子線程獲取數(shù)據(jù):" + threadLocal.get());
        }).start();
    }
}

父線程獲取數(shù)據(jù):6
子線程獲取數(shù)據(jù):null

public class ThreadLocalTest {

    public static void main(String[] args) {
        InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
        threadLocal.set(6);
        System.out.println("父線程獲取數(shù)據(jù):" + threadLocal.get());

        new Thread(() -> {
            System.out.println("子線程獲取數(shù)據(jù):" + threadLocal.get());
        }).start();
    }
}

父線程獲取數(shù)據(jù):6
子線程獲取數(shù)據(jù):6

ThreadLocal有哪些用途雷客?

1.在spring事務(wù)中,保證一個(gè)線程下桥狡,一個(gè)事務(wù)的多個(gè)操作拿到的是一個(gè)Connection搅裙。

2.在hiberate中管理session。

3.在JDK8之前裹芝,為了解決SimpleDateFormat的線程安全問題部逮。

4.獲取當(dāng)前登錄用戶上下文。

5.臨時(shí)保存權(quán)限數(shù)據(jù)嫂易。

6.使用MDC保存日志信息兄朋。

ThreadLocal的應(yīng)用場(chǎng)景和使用注意點(diǎn)

ThreadLocal的很重要一個(gè)注意點(diǎn),就是使用完怜械,要手動(dòng)調(diào)用remove()颅和。

而ThreadLocal的應(yīng)用場(chǎng)景主要有以下這幾種:

使用日期工具類,當(dāng)用到SimpleDateFormat缕允,使用ThreadLocal保證線性安全

全局存儲(chǔ)用戶信息(用戶信息存入ThreadLocal峡扩,那么當(dāng)前線程在任何地方需要時(shí),都可以使用)

保證同一個(gè)線程障本,獲取的數(shù)據(jù)庫連接Connection是同一個(gè)教届,使用ThreadLocal來解決線程安全的問題

使用MDC保存日志信息。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末彼绷,一起剝皮案震驚了整個(gè)濱河市巍佑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寄悯,老刑警劉巖萤衰,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異猜旬,居然都是意外死亡脆栋,警方通過查閱死者的電腦和手機(jī)倦卖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來椿争,“玉大人怕膛,你說我怎么就攤上這事∏刈伲” “怎么了褐捻?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)椅邓。 經(jīng)常有香客問我柠逞,道長(zhǎng),這世上最難降的妖魔是什么景馁? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任板壮,我火速辦了婚禮,結(jié)果婚禮上合住,老公的妹妹穿的比我還像新娘绰精。我一直安慰自己,他們只是感情好透葛,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布笨使。 她就那樣靜靜地躺著,像睡著了一般获洲。 火紅的嫁衣襯著肌膚如雪阱表。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天贡珊,我揣著相機(jī)與錄音最爬,去河邊找鬼。 笑死门岔,一個(gè)胖子當(dāng)著我的面吹牛爱致,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寒随,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼糠悯,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了妻往?” 一聲冷哼從身側(cè)響起互艾,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎讯泣,沒想到半個(gè)月后纫普,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡好渠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年昨稼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了节视。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡假栓,死狀恐怖寻行,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情匾荆,我是刑警寧澤拌蜘,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站牙丽,受9級(jí)特大地震影響拦坠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜剩岳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望入热。 院中可真熱鬧绰播,春花似錦蠢箩、人聲如沸谬泌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至邻悬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間础米,已是汗流浹背医寿。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工蘑斧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓职辅,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親秀鞭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

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