JDK成長記17:Atomic類的原理—CAS+valotile

file

經(jīng)過volatile和synchronize關(guān)鍵字的底層原理的洗禮,不知道你是否有一種感覺翔脱,要想弄明白它們的原理是一個(gè)非常難的過程。為什么費(fèi)這么大的力氣要弄明白這些并發(fā)基礎(chǔ)知識呢利诺?

其實(shí)是為了之后更好的掌握并發(fā)組件赞庶、并發(fā)集合這些內(nèi)容。JDK中的juc(并發(fā)包)的知識大體可以分為如下幾塊:

file

并發(fā)基礎(chǔ)中除了volatile眷蚓、synchronied鼻种、線程狀態(tài)變化之外,還有很重要的兩個(gè)知識CAS和AQS沙热。而其他并發(fā)組件和集合都是基于這些知識來實(shí)現(xiàn)的工具而已叉钥。

這一節(jié)我們通過Atomic類來學(xué)習(xí)下它的底層原理,實(shí)際它的底層通過CAS+volatile的原理來實(shí)現(xiàn)的篙贸。我們來具體看看:

  • JDK中的CAS如何實(shí)現(xiàn)的投队?又是如何應(yīng)用在Atomic中的?
  • CAS的自旋性能問題怎么優(yōu)化爵川?
  • CAS的ABA問題怎么解決蛾洛?

在Java代碼中,CAS是如何應(yīng)用在Atomic中的雁芙?

<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">在Java代碼中轧膘,CAS是如何應(yīng)用在Atomic中的?</span></h3></div>

之前學(xué)習(xí)synchronized的時(shí)候兔甘,就接觸過CAS谎碍。CAS一般稱作Compare And Swap操作。操作流程如下:

file

上面CAS操作簡單描述可以如下:當(dāng)更新一個(gè)值從E->V時(shí)洞焙,更新的時(shí)候需要讀取E最新的值N蟆淀,如果發(fā)生了變化,也就是當(dāng)E!=N澡匪,就不會(huì)更新成功熔任,重新嘗試,否則更新值成功唁情,變?yōu)閂疑苔。

來看一個(gè)Demo:

private static AtomicInteger j = new AtomicInteger(0);

public static void main(String[] args) {
  for (int i = 0; i < 10; i++) {
    new Thread(() -> {
      System.out.println(AtomicIntegerDemo.j.incrementAndGet());
    }).start();
  }
}

這段程序有10個(gè)線程,它們可能同時(shí)更新J的值甸鸟。但是輸出結(jié)果是按順序輸出了0到10的數(shù)字惦费。這是因?yàn)樵贏tomicInteger底層兵迅,每個(gè)線程的更新都是CAS操作,它保證了多線程修改同一個(gè)值的原子性薪贫。

首先你可以先看一下AtomicInteger 整體脈絡(luò):

file

根據(jù)之前分析源碼的思想恍箭,先脈絡(luò)后細(xì)節(jié),你應(yīng)該可以看出瞧省,它核心脈絡(luò)就是一個(gè)Unsafe對象扯夭,int的value。如下

public class AtomicInteger extends Number implements java.io.Serializable {
  // setup to use Unsafe.compareAndSwapInt for updates
  private static final Unsafe unsafe = Unsafe.getUnsafe();

  private static final long valueOffset;


  static {
    try {
      valueOffset = unsafe.objectFieldOffset
        (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
  }

  private volatile int value;

}

核心變量:

  • value是一個(gè)volatile的int值鞍匾。通過volatile的可見性勉抓,可以保證有一個(gè)線程更新了,其他線程可以得到最新值候学。
  • valueOffset,類初始化的時(shí)候,來進(jìn)行執(zhí)行的纵散,valueOffset梳码,value這個(gè)字段在AtomicInteger這個(gè)類中的偏移量,在底層伍掀,這個(gè)類是有自己對應(yīng)的結(jié)構(gòu)的掰茶,無論是在磁盤的.class文件里,還是在JVM內(nèi)存中蜜笤。大概可以理解為:value這個(gè)字段具體是在AtomicInteger這個(gè)類的哪個(gè)位置濒蒋,offset,偏移量把兔,這個(gè)是很底層的操作沪伙,是通過unsafe來實(shí)現(xiàn)的。剛剛在類初始化的時(shí)候县好,就會(huì)完成這個(gè)操作的围橡,final的,一旦初始化完畢缕贡,就不會(huì)再變更了翁授。
  • Usafe類,進(jìn)行真正CAS操作的類

當(dāng)通過new AtomicInteger(0)這句話創(chuàng)建的對象晾咪,實(shí)際是給value賦值了一個(gè)初始值0而已收擦。(int默認(rèn)就是0)

 public AtomicInteger(int initialValue) {
    value = initialValue;
  }

現(xiàn)在你可以得到AtomicInteger的核心組件圖如下:

file

接著每個(gè)線程會(huì)調(diào)用incrementAndGet這個(gè)方法:

  public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
  }

可以看到直接調(diào)用了Unsafe的getAndAddInt方法,如下:

  public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
      v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
  }

(可以下載OpenJDK源碼或訪問http://hg.openjdk.java.net/jdk/jdk/file/a1ee9743f4ee/jdk/src/share/classes/sun/misc/Unsafe.java

上面這段代碼從脈絡(luò)上可以看出來谍倦,是經(jīng)典的CAS操作塞赂,首先通過一次volatile讀,讀到最新的v值昼蛀,之后再通過compareAndSwapInt這個(gè)方法减途,進(jìn)行CAS操作酣藻。如果CAS操作失敗,會(huì)返回false鳍置,while循環(huán)繼續(xù)執(zhí)行辽剧,進(jìn)行自旋,重新嘗試CAS操作税产。如果CAS更新操作成功怕轿,返回原值。

返回原值之后辟拷,incrementAndGet進(jìn)行了+1操作撞羽,incrementAndGet方法返回,就會(huì)的得到更新后的值衫冻。

如下圖所示:

file

在Java代碼層面诀紊,很多Atomic原子類底層核心的原理就是CAS,有人也把這種操作稱為無鎖化隅俘,樂觀鎖邻奠,自旋鎖之類的。(稱呼這種東西是用來交流的为居,大家能明白就好碌宴,不要太過較真)。

CAS操作每次嘗試修改的時(shí)候蒙畴,就對比一下贰镣,有沒有人修改過這個(gè)值,沒有人修改膳凝,自己就修改碑隆,如果有人修改過,就重新查出來最新的值蹬音,再次重復(fù)那個(gè)過程干跛。

而AtomicLong、AtomicLongArray祟绊、AtomicBoolean這些原子操作類和AtomicIneger底層是類似的楼入,都是通過Unsafe類來做到CAS操作的。

file

也正是這種操作牧抽,可以保證多線程更新的時(shí)候的原子性嘉熊,如下圖所示:

file

在JVM中的CAS如何實(shí)現(xiàn)的?

<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">在JVM中的CAS如何實(shí)現(xiàn)的扬舒?</span></h3></div>

具體的CAS操作方法都是native的方法阐肤,是通過C++的代碼實(shí)現(xiàn)的。

  public native int   getIntVolatile(Object o, long offset);
  
  public final native boolean compareAndSwapInt(Object o, long offset,
                         int expected,
                         int x);

這就需要我們進(jìn)一步深入JVM來看下CAS是如何實(shí)現(xiàn)的。我給大家找到了對應(yīng)的C++代碼如下:

// unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))

 UnsafeWrapper("Unsafe_CompareAndSwapInt");

 oop p = JNIHandles::resolve(obj);

 jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);

 return (jint)(Atomic::cmpxchg(x, addr, e)) == e;

UNSAFE_END

通過觀察孕惜,可以發(fā)現(xiàn)愧薛,核心的CAS操作是通過Atomic::cmpxchg(x, addr, e)這個(gè)方法實(shí)現(xiàn)的。這個(gè)方法從名字上cmpxchg就可以猜到是compare and exchange和compare and swap(CAS)是一個(gè)意思衫画。

但是你繼續(xù)跟蹤代碼會(huì)發(fā)現(xiàn)毫炉,這個(gè)Atomic::cmpxchg方法有很多實(shí)現(xiàn)。如下圖右側(cè):

file

因?yàn)椴煌牟僮飨到y(tǒng)和CPU對應(yīng)的CAS指令可能有所不同削罩,所以有了不同的CAS實(shí)現(xiàn)瞄勾。

這里可以看一下atomic_linux_x86.inline.hpp 93行的這個(gè)實(shí)現(xiàn)方法。代碼如下:

#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "

inline jint   Atomic::cmpxchg  (jint   exchange_value, volatile jint*   dest, jint   compare_value) {

 int mp = os::is_MP();

 __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
          : "=a" (exchange_value)
          : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
          : "cc", "memory");

 return exchange_value;
}

上面這段代碼其實(shí)就是CAS最底層的實(shí)現(xiàn)方式弥激。

首先通過is_MP 這個(gè)判斷表示是否是多核CPU處理的意思(Multi Processor ) 进陡。

接著如果是的話,asm表示匯編指令微服,發(fā)送一條原子性的匯編指令趾疚。也就是說CAS底層實(shí)際通過類似于lock cmpxchgl 這種匯編的指令,通知CPU進(jìn)行原子性的更新以蕴。這其實(shí)一個(gè)輕量的loc指令糙麦,可以讓CPU保證原子性的操作,所以說CAS是自旋鎖,是有道理的舒裤。

好了,到這里CAS在JVM層面的具體實(shí)現(xiàn)你應(yīng)該已經(jīng)了解了觉吭。接下來我們來看CAS的產(chǎn)生的兩個(gè)問題腾供。

CAS的自旋無限循環(huán)性能問題怎么優(yōu)化?

<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">CAS的自旋無限循環(huán)性能問題怎么優(yōu)化鲜滩?</span></h3></div>

不知道大家想到過沒伴鳖,CAS操作有一個(gè)很大的問題,如果在高并發(fā)徙硅,多線程更新的時(shí)候榜聂,會(huì)造成大量線程進(jìn)行自旋,消耗CPU資源嗓蘑。這樣的性能是很不好的(當(dāng)然比synchronized還是要好很多的)须肆。

為了解決這一個(gè)問題,Java 8提供的一個(gè)對AtomicLong改進(jìn)后的一個(gè)類桩皿,LongAdder豌汇。它具備一個(gè)分段CAS的原子操作類。讓我們來看一下它的分段CAS優(yōu)化的思想

什么叫分段CAS,或者說是分段遷移呢泄隔?

意思就是拒贱,當(dāng)某一個(gè)線程如果對一個(gè)值更新是,可以看對這個(gè)值進(jìn)行分段更新,每一段叫做一個(gè)Cell逻澳,在更新每一個(gè)Cell的時(shí)候闸天,發(fā)現(xiàn)說出現(xiàn)了很難更新它的值,出現(xiàn)了多次 CAS失敗了斜做,自旋的時(shí)候苞氮,進(jìn)行自動(dòng)遷移段,它會(huì)去嘗試更新別的分段Cell的值陨享,這樣的話就可以讓一個(gè)線程不會(huì)盲目的CAS自旋等待一個(gè)更新分段cell的值葱淳。

LongAdder正是基于這個(gè)思想來實(shí)現(xiàn)的∨坠茫基本原理如下圖所示:

file

LongAdder先嘗試一次cas更新赞厕,如果失敗會(huì)轉(zhuǎn)而通過Cell[]的方式更新值,如果計(jì)算index的方式足夠散列定硝,那么在并發(fā)量大的情況下皿桑,多個(gè)線程定位到同一個(gè)cell的概率也就越低,這有點(diǎn)類似于分段鎖的意思蔬啡。

但是要注意的是sum方法在并發(fā)情況下sum的值不精確诲侮,reset方法不是線程安全的。

  public void reset() {
    Cell[] as = cells; Cell a;
    base = 0L;
   if (as != null) {
      for (int i = 0; i < as.length; ++i) {
        if ((a = as[i]) != null)
          a.value = 0L;
      }
    }
  }


  public long sum() {
    Cell[] as = cells; Cell a;
    long sum = base;
    if (as != null) {
      for (int i = 0; i < as.length; ++i) {
        if ((a = as[i]) != null)
          sum += a.value;
      }
    }
    return sum;

  }

關(guān)于sum不精確

sum方法的實(shí)現(xiàn)很簡單箱蟆,其實(shí)就是 base + sum(cells)沟绪。由上述源碼可以發(fā)現(xiàn),sum執(zhí)行時(shí)空猜,最終返回的是sum局部變量绽慈,初始被復(fù)制為base,而最終返回時(shí)辈毯,很可能base已經(jīng)被更新了坝疼,而此時(shí)局部變量sum不會(huì)更新,造成不一致谆沃。其次钝凶,這里對cell的讀取也無法保證是最后一次寫入的值。

所以唁影,sum方法在沒有并發(fā)的情況下耕陷,可以獲得正確的結(jié)果。

關(guān)于reset不線程安全

因?yàn)閞eset方法并不是原子操作据沈,它先將base重置成0攻人,假設(shè)此時(shí)CPU切走執(zhí)行sum傻昙,此時(shí)的sum就變成了減去base后的值。也就是說,在并發(fā)量大的情況下李剖,執(zhí)行完此方法并不能保證其他線程看到的是重置后的結(jié)果。所以要慎用。

在實(shí)際工作中,可根據(jù)LongAdder和AtomicLong的特點(diǎn)來使用這兩個(gè)工具肿嘲。 當(dāng)需要在高并發(fā)下有較好的性能表現(xiàn),且對值的精確度要求不高時(shí)筑公,可以使用LongAdder(例如網(wǎng)站訪問人數(shù)計(jì)數(shù))雳窟。 當(dāng)需要保證線程安全,可允許一些性能損耗匣屡,要求高精度時(shí)封救,可以使用AtomicLong。LongAdder捣作,替代AtomicLong,完全可以對心跳計(jì)數(shù)器來使用LongAdder誉结。

CAS的ABA問題怎么解決?

<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">CAS的ABA問題怎么解決券躁?</span></h3></div>

最后我們聊一下CAS產(chǎn)生的ABA問題惩坑。

什么是ABA問題?

如果某個(gè)值一開始是A也拜,后來變成了B以舒,然后又變成了A,你本來期望的是值如果是第一個(gè)A才會(huì)設(shè)置新值慢哈,結(jié)果第二個(gè)A一比較也ok蔓钟,也設(shè)置了新值,跟期望是不符合的卵贱。

怎么解決呢滥沫?

解決辦法也很簡單:加一個(gè)類似于版本號的東西,比如郵戳int stamp之類的艰赞。記錄更新的次數(shù)即可佣谐,比較的時(shí)候不光比較value也要比較郵戳肚吏。

所以atomic包里有AtomicStampedReference類方妖,就是會(huì)比較兩個(gè)值的引用是否一致,如果一致罚攀,才會(huì)設(shè)置新值

你可以自己研究一下它的的源碼党觅。它CAS核心代碼如下:

  // AtomicStampedReference
  public boolean compareAndSet(V  expectedReference,
                 V  newReference,
                 int expectedStamp,
                 int newStamp) {

    Pair<V> current = pair;
    return
      expectedReference == current.reference &&
      expectedStamp == current.stamp &&
      ((newReference == current.reference &&
       newStamp == current.stamp) ||
       casPair(current, Pair.of(newReference, newStamp)));
  }

  private boolean casPair(Pair<V> cmp, Pair<V> val) {
    return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
  }

最后還要說明的一點(diǎn),就是Atomic類不能保證多變量原子問題斋泄,一般的AtomicInteger杯瞻,只能保證一個(gè)變量的原子性。

但是如果多個(gè)變量炫掐?你可以用AtomicReference魁莉,這個(gè)是封裝自定義對象的,多個(gè)變量可以放一個(gè)自定義對象里,然后他會(huì)檢查這個(gè)對象的引用是不是一個(gè)旗唁。(注意對象中的變量如果不是Atomic,操作的時(shí)候不保證原子性畦浓,只能保證操作AtomicReference泛型對應(yīng)的這個(gè)對象的引用時(shí)是原子的。)

小結(jié)

<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">小結(jié)</span></h3></div>

今天检疫,了解Atomic的類讶请。這里給大家小結(jié)下Atomic類底層原理都是CAS,原理都是類似的屎媳。主要分為如下幾類:

1夺溢、AtomicInteger/AtomicLong/AtomicBoolean/AtomicReference是關(guān)于對變量的原子CAS更新。

2烛谊、AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray是關(guān)于對數(shù)組的原子CAS更新风响。

3、AtomicIntegerFieldUpdater<T>/AtomicLongFieldUpdater<T>/AtomicReferenceFieldUpdater<T,V>是基于反射的CAS原子更新某個(gè)類的字段晒来。

學(xué)完這一節(jié)你應(yīng)該掌握了如下知識

  • CAS的java層面原理(Unsafe+volatile value)
  • CAS的JVM層面(C++/CPU匯編指令lock cmpxchgl前綴指令)
  • ABA問題钞诡、無限循環(huán)性能問題、多變量原子更新問題
  • 分段遷移CAS思想湃崩、郵戳版本號思想荧降、原子引用思想

本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末攒读,一起剝皮案震驚了整個(gè)濱河市朵诫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌薄扁,老刑警劉巖剪返,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異邓梅,居然都是意外死亡脱盲,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門日缨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钱反,“玉大人,你說我怎么就攤上這事匣距∶娓纾” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵毅待,是天一觀的道長尚卫。 經(jīng)常有香客問我,道長尸红,這世上最難降的妖魔是什么吱涉? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任刹泄,我火速辦了婚禮,結(jié)果婚禮上怎爵,老公的妹妹穿的比我還像新娘循签。我一直安慰自己,他們只是感情好疙咸,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布县匠。 她就那樣靜靜地躺著,像睡著了一般撒轮。 火紅的嫁衣襯著肌膚如雪乞旦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天题山,我揣著相機(jī)與錄音兰粉,去河邊找鬼。 笑死顶瞳,一個(gè)胖子當(dāng)著我的面吹牛玖姑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播慨菱,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼焰络,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了符喝?” 一聲冷哼從身側(cè)響起闪彼,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎协饲,沒想到半個(gè)月后畏腕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茉稠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年描馅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片而线。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡铭污,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吞获,到底是詐尸還是另有隱情况凉,我是刑警寧澤谚鄙,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布各拷,位于F島的核電站,受9級特大地震影響闷营,放射性物質(zhì)發(fā)生泄漏烤黍。R本人自食惡果不足惜知市,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望速蕊。 院中可真熱鬧嫂丙,春花似錦、人聲如沸规哲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唉锌。三九已至隅肥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間袄简,已是汗流浹背腥放。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绿语,地道東北人秃症。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像吕粹,于是被迫代替她去往敵國和親种柑。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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