深度揭秘Netty中的FastThreadLocal為什么比ThreadLocal效率更高寝优?

file

閱讀這篇文章之前且轨,建議先閱讀和這篇文章關(guān)聯(lián)的內(nèi)容。

1. 詳細(xì)剖析分布式微服務(wù)架構(gòu)下網(wǎng)絡(luò)通信的底層實(shí)現(xiàn)原理(圖解)

2. (年薪60W的技巧)工作了5年礁叔,你真的理解Netty以及為什么要用嗎?(深度干貨)

3. 深度解析Netty中的核心組件(圖解+實(shí)例)

4. BAT面試必問(wèn)細(xì)節(jié):關(guān)于Netty中的ByteBuf詳解

5. 通過(guò)大量實(shí)戰(zhàn)案例分解Netty中是如何解決拆包黏包問(wèn)題的?

6. 基于Netty實(shí)現(xiàn)自定義消息通信協(xié)議(協(xié)議設(shè)計(jì)及解析應(yīng)用實(shí)戰(zhàn))

7. 全網(wǎng)最詳細(xì)最齊全的序列化技術(shù)及深度解析與應(yīng)用實(shí)戰(zhàn)

8. 手把手教你基于Netty實(shí)現(xiàn)一個(gè)基礎(chǔ)的RPC框架(通俗易懂)

9. (年薪60W分水嶺)基于Netty手寫實(shí)現(xiàn)RPC框架進(jìn)階篇(帶注冊(cè)中心和注解)

FastThreadLocal的實(shí)現(xiàn)與J.U.C包中的ThreadLocal非常類似迄薄。

了解過(guò)ThreadLocal原理的同學(xué)應(yīng)該都清楚琅关,它有幾個(gè)關(guān)鍵的對(duì)象.

  1. Thread
  2. ThreadLocalMap
  3. ThreadLocal

同樣,Netty專門為FastThreadLocal量身打造了FastThreadLocalThreadInternalThreadLocalMap兩個(gè)重要的類讥蔽。下面我們看下這兩個(gè)類是如何實(shí)現(xiàn)的涣易。

PS,如果不懂ThreadLocal的朋友冶伞,可以看我這篇文章:ThreadLocal的使用及原理分析

FastThreadLocalThread是對(duì)Thread類的一層包裝新症,每個(gè)線程對(duì)應(yīng)一個(gè)InternalThreadLocalMap實(shí)例。只有FastThreadLocalFastThreadLocalThread組合使用時(shí)响禽,才能發(fā)揮 FastThreadLocal的性能優(yōu)勢(shì)徒爹。首先看下FastThreadLocalThread的源碼定義:

public class FastThreadLocalThread extends Thread {

    private InternalThreadLocalMap threadLocalMap;
    // 省略其他代碼
}

可以看出 FastThreadLocalThread 主要擴(kuò)展了 InternalThreadLocalMap 字段,我們可以猜測(cè)到 FastThreadLocalThread 主要使用 InternalThreadLocalMap 存儲(chǔ)數(shù)據(jù)芋类,而不再是使用 Thread 中的 ThreadLocalMap隆嗅。所以想知道 FastThreadLocalThread 高性能的奧秘,必須要了解 InternalThreadLocalMap 的設(shè)計(jì)原理侯繁。

InternalThreadLocalMap

public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {

    private static final int DEFAULT_ARRAY_LIST_INITIAL_CAPACITY = 8;

    private static final int STRING_BUILDER_INITIAL_SIZE;

    private static final int STRING_BUILDER_MAX_SIZE;

    public static final Object UNSET = new Object();

    private BitSet cleanerFlags;
    private InternalThreadLocalMap() {
        indexedVariables = newIndexedVariableTable();
    }
    private static Object[] newIndexedVariableTable() {
        Object[] array = new Object[INDEXED_VARIABLE_TABLE_INITIAL_SIZE];
        Arrays.fill(array, UNSET);
        return array;
    }
    public static int lastVariableIndex() {
        return nextIndex.get() - 1;
    }

    public static int nextVariableIndex() {
        int index = nextIndex.getAndIncrement();
        if (index < 0) {
            nextIndex.decrementAndGet();
            throw new IllegalStateException("too many thread-local indexed variables");
        }
        return index;
    }
    // 省略

}

從 InternalThreadLocalMap 內(nèi)部實(shí)現(xiàn)來(lái)看胖喳,與 ThreadLocalMap 一樣都是采用數(shù)組的存儲(chǔ)方式。

了解ThreadLocal的同學(xué)都知道贮竟,它內(nèi)部也是采用數(shù)組的方式來(lái)實(shí)現(xiàn)hash表丽焊,對(duì)于hash沖突,采用了線性探索的方式來(lái)實(shí)現(xiàn)坝锰。

但是 InternalThreadLocalMap 并沒(méi)有使用線性探測(cè)法來(lái)解決 Hash 沖突粹懒,而是在 FastThreadLocal 初始化的時(shí)候分配一個(gè)數(shù)組索引 index,index 的值采用原子類 AtomicInteger 保證順序遞增顷级,通過(guò)調(diào)用 InternalThreadLocalMap.nextVariableIndex() 方法獲得凫乖。然后在讀寫數(shù)據(jù)的時(shí)候通過(guò)數(shù)組下標(biāo) index 直接定位到 FastThreadLocal 的位置,時(shí)間復(fù)雜度為 O(1)。如果數(shù)組下標(biāo)遞增到非常大帽芽,那么數(shù)組也會(huì)比較大删掀,所以 FastThreadLocal 是通過(guò)空間換時(shí)間的思想提升讀寫性能。

下面通過(guò)一幅圖描述 InternalThreadLocalMap导街、index 和 FastThreadLocal 之間的關(guān)系披泪。

image-20211123112056607

通過(guò)上面 FastThreadLocal 的內(nèi)部結(jié)構(gòu)圖,我們對(duì)比下與 ThreadLocal 有哪些區(qū)別呢搬瑰?

FastThreadLocal 使用 Object 數(shù)組替代了 Entry 數(shù)組款票,Object[0] 存儲(chǔ)的是一個(gè)Set<FastThreadLocal<?>> 集合。

從數(shù)組下標(biāo) 1 開(kāi)始都是直接存儲(chǔ)的 value 數(shù)據(jù)泽论,不再采用 ThreadLocal 的鍵值對(duì)形式進(jìn)行存儲(chǔ)艾少。

假設(shè)現(xiàn)在我們有一批數(shù)據(jù)需要添加到數(shù)組中,分別為 value1翼悴、value2缚够、value3、value4鹦赎,對(duì)應(yīng)的 FastThreadLocal 在初始化的時(shí)候生成的數(shù)組索引分別為 1谍椅、2、3古话、4雏吭。如下圖所示。

image-20211123112505405

至此陪踩,我們已經(jīng)對(duì) FastThreadLocal 有了一個(gè)基本的認(rèn)識(shí)思恐,下面我們結(jié)合具體的源碼分析 FastThreadLocal 的實(shí)現(xiàn)原理。

FastThreadLocal的set方法源碼分析

在講解源碼之前膊毁,我們回過(guò)頭看下上文中的 ThreadLocal 示例,如果把示例中 ThreadLocal 替換成 FastThread基跑,應(yīng)當(dāng)如何使用呢婚温?

public class FastThreadLocalTest {

    private static final FastThreadLocal<String> THREAD_NAME_LOCAL = new FastThreadLocal<>();
    private static final FastThreadLocal<TradeOrder> TRADE_THREAD_LOCAL = new FastThreadLocal<>();
    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            int tradeId = i;
            String threadName = "thread-" + i;
            new FastThreadLocalThread(() -> {
                THREAD_NAME_LOCAL.set(threadName);
                TradeOrder tradeOrder = new TradeOrder(tradeId, tradeId % 2 == 0 ? "已支付" : "未支付");
                TRADE_THREAD_LOCAL.set(tradeOrder);
                System.out.println("threadName: " + THREAD_NAME_LOCAL.get());
                System.out.println("tradeOrder info:" + TRADE_THREAD_LOCAL.get());
            }, threadName).start();

        }
    }
}

可以看出,F(xiàn)astThreadLocal 的使用方法幾乎和 ThreadLocal 保持一致媳否,只需要把代碼中 Thread栅螟、ThreadLocal 替換為 FastThreadLocalThread 和 FastThreadLocal 即可,Netty 在易用性方面做得相當(dāng)棒篱竭。下面我們重點(diǎn)對(duì)示例中用得到 FastThreadLocal.set()/get() 方法做深入分析力图。

首先看下 FastThreadLocal.set() 的源碼:

public final void set(V value) {
    if (value != InternalThreadLocalMap.UNSET) {
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
        setKnownNotUnset(threadLocalMap, value);
    } else {
        remove();
    }
}

FastThreadLocal.set() 方法實(shí)現(xiàn)并不難理解,先抓住代碼主干掺逼,一步步進(jìn)行拆解分析吃媒。set() 的過(guò)程主要分為三步:

  1. 判斷 value 是否為缺省值,如果等于缺省值,那么直接調(diào)用 remove() 方法赘那。這里我們還不知道缺省值和 remove() 之間的聯(lián)系是什么刑桑,我們暫且把 remove() 放在最后分析。
  2. 如果 value 不等于缺省值募舟,接下來(lái)會(huì)獲取當(dāng)前線程的 InternalThreadLocalMap祠斧。
  3. 然后將 InternalThreadLocalMap 中對(duì)應(yīng)數(shù)據(jù)替換為新的 value。

InternalThreadLocalMap.get()

先來(lái)看InternalThreadLocalMap.get()方法:

public static InternalThreadLocalMap get() {
    Thread thread = Thread.currentThread();
    if (thread instanceof FastThreadLocalThread) {
        return fastGet((FastThreadLocalThread) thread);
    } else {
        return slowGet();
    }
}

如果thread實(shí)例類型是FastThreadLocalThread拱礁,則調(diào)用fastGet()琢锋。

InternalThreadLocalMap.get() 邏輯很簡(jiǎn)單.

  1. 如果當(dāng)前線程是 FastThreadLocalThread 類型,那么直接通過(guò) fastGet() 方法獲取 FastThreadLocalThread 的 threadLocalMap 屬性即可
  2. 如果此時(shí) InternalThreadLocalMap 不存在呢灶,直接創(chuàng)建一個(gè)返回吴超。

關(guān)于 InternalThreadLocalMap 的初始化在上文中已經(jīng)介紹過(guò),它會(huì)初始化一個(gè)長(zhǎng)度為 32 的 Object 數(shù)組填抬,數(shù)組中填充著 32 個(gè)缺省對(duì)象 UNSET 的引用烛芬。

private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
  InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
  if (threadLocalMap == null) {
    thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
  }
  return threadLocalMap;
}

否則,則調(diào)用slowGet()飒责,從代碼實(shí)現(xiàn)來(lái)看赘娄,slowGet() 是針對(duì)非 FastThreadLocalThread 類型的線程發(fā)起調(diào)用時(shí)的一種兜底方案。如果當(dāng)前線程不是 FastThreadLocalThread宏蛉,內(nèi)部是沒(méi)有 InternalThreadLocalMap 屬性的遣臼,Netty 在 UnpaddedInternalThreadLocalMap 中保存了一個(gè) JDK 原生的 ThreadLocal,ThreadLocal 中存放著 InternalThreadLocalMap拾并,此時(shí)獲取 InternalThreadLocalMap 就退化成 JDK 原生的 ThreadLocal 獲取揍堰。

private static InternalThreadLocalMap slowGet() {
  InternalThreadLocalMap ret = slowThreadLocalMap.get();
  if (ret == null) {
    ret = new InternalThreadLocalMap();
    slowThreadLocalMap.set(ret);
  }
  return ret;
}

setKnownNotUnset

獲取 InternalThreadLocalMap 的過(guò)程已經(jīng)講完了,下面看下 setKnownNotUnset() 如何將數(shù)據(jù)添加到 InternalThreadLocalMap 的嗅义。

private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
    if (threadLocalMap.setIndexedVariable(index, value)) {
        addToVariablesToRemove(threadLocalMap, this);
    }
}

setKnownNotUnset() 主要做了兩件事:

  1. 找到數(shù)組下標(biāo) index 位置屏歹,設(shè)置新的 value。
  2. 將 FastThreadLocal 對(duì)象保存到待清理的 Set 中之碗。

首先我們看下第一步 threadLocalMap.setIndexedVariable() 的源碼實(shí)現(xiàn):

public boolean setIndexedVariable(int index, Object value) {
    Object[] lookup = indexedVariables;
    if (index < lookup.length) {
        Object oldValue = lookup[index];
        lookup[index] = value;
        return oldValue == UNSET;
    } else {
        expandIndexedVariableTableAndSet(index, value);
        return true;
    }
}

indexedVariables 就是 InternalThreadLocalMap 中用于存放數(shù)據(jù)的數(shù)組蝙眶,如果數(shù)組容量大于 FastThreadLocal 的 index 索引,那么直接找到數(shù)組下標(biāo) index 位置將新 value 設(shè)置進(jìn)去褪那,事件復(fù)雜度為 O(1)幽纷。在設(shè)置新的 value 之前,會(huì)將之前 index 位置的元素取出博敬,如果舊的元素還是 UNSET 缺省對(duì)象友浸,那么返回成功。

如果數(shù)組容量不夠了怎么辦呢偏窝?InternalThreadLocalMap 會(huì)自動(dòng)擴(kuò)容收恢,然后再設(shè)置 value武学。接下來(lái)看看 expandIndexedVariableTableAndSet() 的擴(kuò)容邏輯:

private void expandIndexedVariableTableAndSet(int index, Object value) {
    Object[] oldArray = indexedVariables;
    final int oldCapacity = oldArray.length;
    int newCapacity = index;
    newCapacity |= newCapacity >>>  1;
    newCapacity |= newCapacity >>>  2;
    newCapacity |= newCapacity >>>  4;
    newCapacity |= newCapacity >>>  8;
    newCapacity |= newCapacity >>> 16;
    newCapacity ++;

    Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
    Arrays.fill(newArray, oldCapacity, newArray.length, UNSET);
    newArray[index] = value;
    indexedVariables = newArray;
}

可以看出 InternalThreadLocalMap 實(shí)現(xiàn)數(shù)組擴(kuò)容幾乎和 HashMap 完全是一模一樣的,所以多讀源碼還是可以給我們很多啟發(fā)的派诬。InternalThreadLocalMap 以 index 為基準(zhǔn)進(jìn)行擴(kuò)容劳淆,將數(shù)組擴(kuò)容后的容量向上取整為 2 的次冪。然后將原數(shù)組內(nèi)容拷貝到新的數(shù)組中默赂,空余部分填充缺省對(duì)象 UNSET沛鸵,最終把新數(shù)組賦值給 indexedVariables。

思考關(guān)于基準(zhǔn)擴(kuò)容

思考:為什么 InternalThreadLocalMap 以 index 為基準(zhǔn)進(jìn)行擴(kuò)容缆八,而不是原數(shù)組長(zhǎng)度呢曲掰?

假設(shè)現(xiàn)在初始化了 70 個(gè) FastThreadLocal,但是這些 FastThreadLocal 從來(lái)沒(méi)有調(diào)用過(guò) set() 方法奈辰,此時(shí)數(shù)組還是默認(rèn)長(zhǎng)度 32栏妖。當(dāng)?shù)?index = 70 的 FastThreadLocal 調(diào)用 set() 方法時(shí),如果按原數(shù)組容量 32 進(jìn)行擴(kuò)容 2 倍后奖恰,還是無(wú)法填充 index = 70 的數(shù)據(jù)吊趾。所以使用 index 為基準(zhǔn)進(jìn)行擴(kuò)容可以解決這個(gè)問(wèn)題,但是如果 FastThreadLocal 特別多瑟啃,數(shù)組的長(zhǎng)度也是非常大的论泛。

回到 setKnownNotUnset() 的主流程,向 InternalThreadLocalMap 添加完數(shù)據(jù)之后蛹屿,接下就是將 FastThreadLocal 對(duì)象保存到待清理的 Set 中屁奏。我們繼續(xù)看下 addToVariablesToRemove() 是如何實(shí)現(xiàn)的:

addToVariablesToRemove

private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
    Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
    Set<FastThreadLocal<?>> variablesToRemove;
    if (v == InternalThreadLocalMap.UNSET || v == null) {
        variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
        threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
    } else {
        variablesToRemove = (Set<FastThreadLocal<?>>) v;
    }

    variablesToRemove.add(variable);
}

variablesToRemoveIndex 是采用 static final 修飾的變量,在 FastThreadLocal 初始化時(shí) variablesToRemoveIndex 被賦值為 0错负。InternalThreadLocalMap 首先會(huì)找到數(shù)組下標(biāo)為 0 的元素.

  1. 如果該元素是缺省對(duì)象 UNSET 或者不存在坟瓢,那么會(huì)創(chuàng)建一個(gè) FastThreadLocal 類型的 Set 集合,然后把 Set 集合填充到數(shù)組下標(biāo) 0 的位置犹撒。
  2. 如果數(shù)組第一個(gè)元素不是缺省對(duì)象 UNSET折联,說(shuō)明 Set 集合已經(jīng)被填充,直接強(qiáng)轉(zhuǎn)獲得 Set 集合即可识颊。這就解釋了 InternalThreadLocalMap 的 value 數(shù)據(jù)為什么是從下標(biāo)為 1 的位置開(kāi)始存儲(chǔ)了崭庸,因?yàn)?0 的位置已經(jīng)被 Set 集合占用了。

思考關(guān)于Set集合設(shè)計(jì)

思考:為什么 InternalThreadLocalMap 要在數(shù)組下標(biāo)為 0 的位置存放一個(gè) FastThreadLocal 類型的 Set 集合呢谊囚?這時(shí)候我們回過(guò)頭看下 remove() 方法。

public final void remove(InternalThreadLocalMap threadLocalMap) {
  if (threadLocalMap == null) {
    return;
  }

  Object v = threadLocalMap.removeIndexedVariable(index);
  removeFromVariablesToRemove(threadLocalMap, this);

  if (v != InternalThreadLocalMap.UNSET) {
    try {
      onRemoval((V) v);
    } catch (Exception e) {
      PlatformDependent.throwException(e);
    }
  }
}

在執(zhí)行 remove 操作之前执赡,會(huì)調(diào)用 InternalThreadLocalMap.getIfSet() 獲取當(dāng)前 InternalThreadLocalMap镰踏。

有了之前的基礎(chǔ),理解 getIfSet() 方法就非常簡(jiǎn)單了沙合。

  1. 如果是 FastThreadLocalThread 類型奠伪,直接取 FastThreadLocalThread 中 threadLocalMap 屬性。
  2. 如果是普通線程 Thread,從 ThreadLocal 類型的 slowThreadLocalMap 中獲取绊率。

找到 InternalThreadLocalMap 之后谨敛,InternalThreadLocalMap 會(huì)從數(shù)組中定位到下標(biāo) index 位置的元素,并將 index 位置的元素覆蓋為缺省對(duì)象 UNSET滤否。

接下來(lái)就需要清理當(dāng)前的 FastThreadLocal 對(duì)象脸狸,此時(shí) Set 集合就派上了用場(chǎng),InternalThreadLocalMap 會(huì)取出數(shù)組下標(biāo) 0 位置的 Set 集合藐俺,然后刪除當(dāng)前 FastThreadLocal炊甲。最后 onRemoval() 方法起到什么作用呢?Netty 只是留了一處擴(kuò)展欲芹,并沒(méi)有實(shí)現(xiàn)卿啡,用戶需要在刪除的時(shí)候做一些后置操作,可以繼承 FastThreadLocal 實(shí)現(xiàn)該方法菱父。

FastThreadLocal.get()源碼分析

再來(lái)看一下 FastThreadLocal.get() 的源碼:

public final V get() {
    InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
    Object v = threadLocalMap.indexedVariable(index);
    if (v != InternalThreadLocalMap.UNSET) {
        return (V) v;
    }

    return initialize(threadLocalMap);
}

首先根據(jù)當(dāng)前線程是否是 FastThreadLocalThread 類型找到 InternalThreadLocalMap颈娜,然后取出從數(shù)組下標(biāo) index 的元素,如果 index 位置的元素不是缺省對(duì)象 UNSET浙宜,說(shuō)明該位置已經(jīng)填充過(guò)數(shù)據(jù)官辽,直接取出返回即可。

public Object indexedVariable(int index) {
  Object[] lookup = indexedVariables;
  return index < lookup.length? lookup[index] : UNSET;
}

如果 index 位置的元素是缺省對(duì)象 UNSET梆奈,那么需要執(zhí)行初始化操作野崇。可以看到亩钟,initialize() 方法會(huì)調(diào)用用戶重寫的 initialValue 方法構(gòu)造需要存儲(chǔ)的對(duì)象數(shù)據(jù).

private V initialize(InternalThreadLocalMap threadLocalMap) {
    V v = null;
    try {
        v = initialValue();
    } catch (Exception e) {
        PlatformDependent.throwException(e);
    }

    threadLocalMap.setIndexedVariable(index, v);
    addToVariablesToRemove(threadLocalMap, this);
    return v;
}

initialValue方法的構(gòu)造方式如下乓梨。

private final FastThreadLocal<String> threadLocal = new FastThreadLocal<String>() {
  @Override
  protected String initialValue() {
    return "hello world";
  }
};

構(gòu)造完用戶對(duì)象數(shù)據(jù)之后,接下來(lái)就會(huì)將它填充到數(shù)組 index 的位置清酥,然后再把當(dāng)前 FastThreadLocal 對(duì)象保存到待清理的 Set 中扶镀。整個(gè)過(guò)程我們?cè)诜治?FastThreadLocal.set() 時(shí)都已經(jīng)介紹過(guò),就不再贅述了焰轻。

到此為止臭觉,F(xiàn)astThreadLocal 最核心的兩個(gè)方法 set()/get() 我們已經(jīng)分析完了。下面有兩個(gè)問(wèn)題我們?cè)偕钊胨伎枷隆?/p>

  1. FastThreadLocal 真的一定比 ThreadLocal 快嗎辱志?答案是不一定的蝠筑,只有使用FastThreadLocalThread 類型的線程才會(huì)更快,如果是普通線程反而會(huì)更慢揩懒。
  2. FastThreadLocal 會(huì)浪費(fèi)很大的空間嗎什乙?雖然 FastThreadLocal 采用的空間換時(shí)間的思路,但是在 FastThreadLocal 設(shè)計(jì)之初就認(rèn)為不會(huì)存在特別多的 FastThreadLocal 對(duì)象已球,而且在數(shù)據(jù)中沒(méi)有使用的元素只是存放了同一個(gè)缺省對(duì)象的引用臣镣,并不會(huì)占用太多內(nèi)存空間辅愿。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市忆某,隨后出現(xiàn)的幾起案子点待,更是在濱河造成了極大的恐慌,老刑警劉巖弃舒,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件癞埠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡棒坏,警方通過(guò)查閱死者的電腦和手機(jī)燕差,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)坝冕,“玉大人徒探,你說(shuō)我怎么就攤上這事∥箍撸” “怎么了测暗?”我有些...
    開(kāi)封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)磨澡。 經(jīng)常有香客問(wèn)我碗啄,道長(zhǎng),這世上最難降的妖魔是什么稳摄? 我笑而不...
    開(kāi)封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任稚字,我火速辦了婚禮,結(jié)果婚禮上厦酬,老公的妹妹穿的比我還像新娘胆描。我一直安慰自己,他們只是感情好仗阅,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布昌讲。 她就那樣靜靜地躺著,像睡著了一般减噪。 火紅的嫁衣襯著肌膚如雪短绸。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天筹裕,我揣著相機(jī)與錄音醋闭,去河邊找鬼。 笑死朝卒,一個(gè)胖子當(dāng)著我的面吹牛目尖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扎运,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼瑟曲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了豪治?” 一聲冷哼從身側(cè)響起洞拨,我...
    開(kāi)封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎负拟,沒(méi)想到半個(gè)月后烦衣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掩浙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年花吟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厨姚。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衅澈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谬墙,到底是詐尸還是另有隱情今布,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布拭抬,位于F島的核電站部默,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏造虎。R本人自食惡果不足惜傅蹂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望算凿。 院中可真熱鬧份蝴,春花似錦、人聲如沸澎媒。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)戒努。三九已至请敦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間储玫,已是汗流浹背侍筛。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撒穷,地道東北人匣椰。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像端礼,于是被迫代替她去往敵國(guó)和親禽笑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子入录,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

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