源碼閱讀——ReentrantLock&AQS

先推薦篇寫AQS的不錯的文章:《從ReentrantLock的實現(xiàn)看AQS的原理及應(yīng)用》河狐、《一文了解AQS(AbstractQueuedSynchronizer)》困檩、《AQS及其組件的核心原理》

AQS 的核心作用是:定義了一套多線程訪問共享資源的同步模板庆猫,解決了實現(xiàn)同步器時涉及的大量細節(jié)問題(線程阻塞等待喚醒的機制铡俐,無鎖狀態(tài)加鎖,有鎖狀態(tài)將線程放在等待隊列排隊獲取鎖)絮供,能夠極大地減少實現(xiàn)工作;這種有效等待,相比于其他死等待或休眠機制崩泡、一方面減少了CPU空耗,另一方面機制能力很強猬膨,可以滿足非常多并發(fā)控制的場景角撞。

//類關(guān)系圖


AQS.jpg

//重要的屬性

方法和屬性值 含義
waitStatus 當前節(jié)點在隊列中的狀態(tài)
prev 前驅(qū)指針
next 后繼指針
thread 表示處于該節(jié)點的線程
nextWaiter 指向下一個處于CONDITION狀態(tài)的節(jié)點
predecessor 返回前驅(qū)節(jié)點,沒有的話拋出npe

waitStatus有下面幾個枚舉值:

方法和屬性值 含義
CANCELLED 為1勃痴,表示線程獲取鎖的請求已經(jīng)取消了
SIGNAL 為-1谒所,表示線程已經(jīng)準備好了,就等資源釋放了
CONDITION 為-2沛申,表示節(jié)點在等待隊列中劣领,節(jié)點線程等待喚醒
PROPAGATE 為-3,當前線程處在SHARED情況下铁材,該字段才會使用
0 當一個Node被初始化的時候的默認值
//非公平鎖
static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        //上鎖的方法尖淘,final表明該方法不能被重寫
        final void lock() {
            //調(diào)用aqs的cas方法來修改state,這個為什么是1著觉,重入鎖:每次會對state+1
            //這兒是直接cas獲取村生,沒有判斷可重入性,該方法只會適用于首次獲取鎖饼丘,如果獲取失敗趁桃,會走下面的acquire(1)
            if (compareAndSetState(0, 1))
                //記錄那個線程獲得了鎖,用于后面重入比對線程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //獲取失敗會再次嘗試獲取
                acquire(1);
        }
        //上面的acquire(1)調(diào)用的是syn類acquire(int arg)方法葬毫,然后又會回調(diào)到子類的這個方法
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

AQS類的方法,該方法非常重要镇辉,是獨占鎖獲取鎖的頂層入庫

public final void acquire(int arg) {
    //如上面說的,先調(diào)用子類獲取贴捡,若失敗忽肛,就會添加到隊列
    //這里面涉及了四個方法tryAcquire;addWaiter烂斋;acquireQueued屹逛;selfInterrupt
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
       selfInterrupt();
    }
}

1.tryAcquire,上面說了最終會調(diào)用子類的nonfairTryAcquire實現(xiàn)汛骂,代碼如下(嘗試直接去獲取資源罕模,如果成功則直接返回(這里體現(xiàn)了非公平鎖,每個線程獲取鎖時會嘗試直接搶占加塞一次帘瞭,而CLH隊列中可能還有別的線程在等待)淑掌;)

//#java.util.concurrent.locks.ReentrantLock
 final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
           //獲取aqs類的state狀態(tài); 這兒有個知識點就是volatile關(guān)鍵字,只>有可見性和禁止重排序蝶念,沒有原子性抛腕,這也是為啥不能保證線程安全的>>原因
            int c = getState();
            //再次判斷state狀態(tài)芋绸,雖然第一次獲取失敗了,到這兒可能已經(jīng)被釋放了担敌,所以再次判斷
            if (c == 0) {
                //這個if里面的類容和第一次獲取鎖一致
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //不等于0的時候摔敛,說明已經(jīng)有線程線程持有資源了,進行重入性判斷全封,就是比對線程對象是否相同
            else if (current == getExclusiveOwnerThread()) {
                //重入對象的計數(shù)+1
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //更新state
                setState(nextc);
                return true;
            }
            return false;
        }

2.addWaiter方法:將該線程加入等待隊列的尾部马昙,并標記為獨占模式

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //將尾結(jié)點賦給pred,目的是騰位置刹悴,后面方便掛最新的mode到CLH隊尾
        Node pred = tail;
        //說明CLH中有其他node節(jié)點在排隊行楞,pre == null說明等待隊列中沒有元素
        if (pred != null) {
            //把新的node節(jié)點的prev指向之前的tail,這樣就把新節(jié)點的前驅(qū)指針掛在之前的隊尾了颂跨,相當于掛了一半上去了
            //最新理解:為啥先掛當前節(jié)點的前驅(qū)敢伸,而不是直接node = tail.next;因為如果要開始就先掛后驅(qū)恒削,那就需要把接下來的compareAndSetTail(pred, node)和 pred.next = node;設(shè)置為原子操作才行池颈,不然在多線程環(huán)境下數(shù)據(jù)混亂
            node.prev = pred;
            // 另一半也需要掛上去,需要把當前的隊尾更新(即更新tail指針)钓丰,涉及并發(fā)躯砰;如果修改失敗說明被別的線程修改了;
           // 如果當前節(jié)點node設(shè)置為隊尾失敗携丁,那就只掛了一半上去琢歇,實際上的隊尾節(jié)點是并發(fā)的另一個線程
            if (compareAndSetTail(pred, node)) {
                // 非原子性操作,由于之前是先掛的前驅(qū)指針梦鉴,所以這句話運行完李茫,當前node就成功入隊,且隊尾tail為當前節(jié)點肥橙,這也是為啥后面喚醒node需要從尾部遍歷的原因之一
                pred.next = node;
                return node;
            }
        }
        // 什么時候到這兒魄宏,tail節(jié)點為空,或者cas設(shè)置tail失敗
        //最新理解:先說問題存筏,為什么要有這個方法:因為掛另一半上去的時候宠互,由于并發(fā)失敗了,當前節(jié)點需要最終掛上去椭坚,就通過該方法自旋予跌,不斷CAS重試,直到掛上去(即成為尾部節(jié)點)
        enq(node);
        return node;
    }
private Node enq(final Node node) {
        //自旋善茎,直到入隊尾成功
        for (;;) {
            Node t = tail;
            // tail為空券册,說明當前隊列里面沒有元素
            if (t == null) { // Must initialize
                // 初始化的頭結(jié)點,并不是當前線程節(jié)點,而是調(diào)用了無參構(gòu)造函數(shù)的節(jié)點
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //該操作和上一個方法一致汁掠,加到隊尾
                node.prev = t;
                // 更新tail指針略吨,指向當前node
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

3.acquireQueued:使線程阻塞在等待隊列中獲取資源,一直獲取到資源后才返回考阱。如果在整個等待過程中被中斷過,則返回true鞠苟,否則返回false(總的來說乞榨,一個線程獲取鎖失敗了,被放入等待隊列当娱,acquireQueued會把放入隊列中的線程不斷去獲取鎖吃既,直到獲取成功或者不再需要獲取(中斷))

 final boolean acquireQueued(final Node node, int arg) {
        //是否拿到資源的標記跨细;默認失敗
        boolean failed = true;
        try {
            //線程在等待過程中是否中斷的標記
            boolean interrupted = false;
            // 自旋鹦倚,什么時候自旋結(jié)束?“前置節(jié)點是頭結(jié)點冀惭,且當前線程獲取鎖成功”
            // 一直自旋會導致cpu資源浪費震叙,所以會對當前節(jié)點的前置節(jié)點進行狀態(tài)判斷覺得是否要將線程掛起,是否掛起要看shouldParkAfterFailedAcquire方法
            for (;;) {
                // 獲取前驅(qū)散休,該方法里面就是把prev給返回
                final Node p = node.predecessor();
                // 如果前驅(qū)p是head媒楼,即該結(jié)點在隊列中第二位,由于CLH隊列的頭結(jié)點是虛節(jié)點戚丸,為空划址,沒有實際的線程信息,所以當前node就是正兒八經(jīng)的首位
                 //所以跳出循環(huán)的條件:前置節(jié)點是頭結(jié)點限府,且當前線程獲取鎖成功
                if (p == head && tryAcquire(arg)) {
                    // 獲取到資源后夺颤,將head指向該結(jié)點;該方法里面將node賦值給head,并把該節(jié)點的線程和prev置空
                    // 相當于指針移動到當前node胁勺,本質(zhì)就是將當前節(jié)點置為虛節(jié)點世澜,把之前的虛節(jié)點引用斷開)
                    // 為什么不是直接remove當前節(jié)點,而是把head指向當前節(jié)點姻几,這個操作的目的是什么
                    // 1.因為當前節(jié)點的里面的waitStatus 后面還有用宜狐,如果移除,需要找個地方保存下來蛇捌;2.如果移除當前節(jié)點抚恒,需要把head和當前節(jié)點的后繼連起來,需要變更4個前驅(qū)后繼的引用络拌,但是直接把當前節(jié)點設(shè)置成頭結(jié)點俭驮,只有三次。
                    setHead(node);
                    p.next = null; // help GC
                    //改變標志位,成功獲取資源
                    failed = false;
                    return interrupted;
                }
                // 執(zhí)行到這兒說明有兩個可能混萝,1:p為頭節(jié)點且當前沒有獲取到鎖(可能是非公平鎖被搶占了)2:p不為頭結(jié)點遗遵;   
                // 這個時候就要判斷當前node是否要被阻塞(被阻塞條件:前驅(qū)節(jié)點的waitStatus為-1),防止無限循環(huán)浪費資源逸嘀。那怎么判斷是否需要阻塞呢车要,就要看下面這個方法了
                // parkAndCheckInterrupt主要用于掛起當前線程,阻塞調(diào)用棧崭倘,返回當前線程的中斷狀態(tài)翼岁。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }

4.shouldParkAfterFailedAcquire:靠前驅(qū)節(jié)點判斷當前線程是否應(yīng)該被阻塞。白話文就是你前面?zhèn)€哥們都在排隊等待被喚醒司光,你不阻塞琅坡,在哪兒瞎折騰啥。(ws == Node.SIGNAL(-1))將會被掛起阻塞

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        // 獲取前驅(qū)結(jié)點的節(jié)點狀態(tài)
        int ws = pred.waitStatus;
        // 說明前驅(qū)結(jié)點處于待喚醒狀態(tài)残家,當前節(jié)點node肯定可以安全的阻塞榆俺,所以返回true
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        //  通過枚舉值我們知道waitStatus>0僅僅只有取消狀態(tài)(該邏輯就是優(yōu)化隊列,剔除取消的節(jié)點)
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                // 循環(huán)向前查找取消節(jié)點坞淮,把取消節(jié)點從隊列中剔除
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            //運行到這里茴晋,說明前驅(qū)節(jié)點處于0、CONDITION或者PROPAGATE狀態(tài)下
            //此時該節(jié)點需要被置為SIGNAL狀態(tài)碾盐,等待被喚醒晃跺。
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

5.取消node節(jié)點

private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;
        // 設(shè)置該節(jié)點不關(guān)聯(lián)任何線程
        node.thread = null;

        // Skip cancelled predecessors  
        // 跳過取消的前驅(qū)節(jié)點
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        // predNext is the apparent node to unsplice. CASes below will
        // fail if not, in which case, we lost race vs another cancel
        // or signal, so no further action is necessary.
       // 執(zhí)行到這一步,說明已經(jīng)優(yōu)化隊列了毫玖,把取消的前驅(qū)節(jié)點清除出隊
       // 將未取消的前驅(qū)節(jié)點的后繼節(jié)點拿到
        Node predNext = pred.next;

        // Can use unconditional write instead of CAS here.
        // After this atomic step, other Nodes can skip past us.
        // Before, we are free of interference from other threads.
        node.waitStatus = Node.CANCELLED;
        //根據(jù)當前節(jié)點存在的位置掀虎,可能存在三種可能
        //1.當前節(jié)點是尾節(jié)點。
        //2. 當前節(jié)點是Head的后繼節(jié)點付枫。
        //3. 當前節(jié)點不是Head的后繼節(jié)點烹玉,也不是尾節(jié)點。

        // If we are the tail, remove ourselves.
        // 如果當前節(jié)點是尾節(jié)點阐滩,將從后往前的第一個非取消狀態(tài)的節(jié)點設(shè)置為尾節(jié)點
        // 更新失敗的話二打,則進入else,如果更新成功掂榔,將tail的后繼節(jié)點設(shè)置為null
        if (node == tail && compareAndSetTail(node, pred)) {
            // 將前驅(qū)節(jié)點的next指向null
            compareAndSetNext(pred, predNext, null);
        } else {
            // If successor needs signal, try to set pred's next-link
            // so it will get one. Otherwise wake it up to propagate.
            int ws;
             // 如果前驅(qū)節(jié)點不是head節(jié)點(即node不在正兒八經(jīng)的首位)继效,1:判斷前驅(qū)節(jié)點是否為SIGNAL,2:如果不是装获,則把前驅(qū)節(jié)點設(shè)置為SINGAL看是否成功
            // 如果1和2中有一個為true瑞信,再判斷當前節(jié)點的線程是否為null
            // 如果上述條件都滿足,把當前節(jié)點的前驅(qū)節(jié)點的后繼指針指向當前節(jié)點的后繼節(jié)點
            //(pred != head)說明前驅(qū)不是head穴豫,當前要取消的node位于隊列中
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    // 把前驅(qū)的preNext指向node的next凡简,這樣node就不在隊列里面了
                    compareAndSetNext(pred, predNext, next);
            } else {
                // 如果當前節(jié)點是head的后繼節(jié)點逼友,或者上述條件不滿足,那就喚醒當前節(jié)點的后繼節(jié)點
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }

Q:某個線程獲取鎖失敗的后續(xù)流程是什么呢秤涩?
A:看本文的流程圖:嘗試CAS再獲取一次帜乞,失敗后自旋到同步隊列尾部,然后自旋去獲取鎖(涉及清除取消的節(jié)點筐眷,進入阻塞掛起)黎烈。

Q:既然說到了排隊等候機制,那么就一定會有某種隊列形成浊竟,這樣的隊列是什么數(shù)據(jù)結(jié)構(gòu)呢怨喘?
A:是CLH變體的FIFO雙端隊列。

Q:處于排隊等候機制中的線程振定,什么時候可以有機會獲取鎖呢?
A:前置節(jié)點為頭結(jié)點肉拓。

Q:那獲取鎖成功之后不是應(yīng)該把當前節(jié)點直接remove移除掉后频,為什么是把 head 指向成當前節(jié)點,這個是什么操作呢暖途?
A:當前節(jié)點獲取鎖成功(不再需要繼續(xù)for 循環(huán)或阻塞等待鎖)卑惜,可以開始執(zhí)行后續(xù)的業(yè)務(wù)邏輯了。這時候不是將當前節(jié)點從隊列移除驻售,而是設(shè)置為頭結(jié)點露久,二方面原因:
因為當前節(jié)點的里面的waitStatus 后面還有用,如果移除欺栗,需要找個地方保存下來毫痕;
如果移除當前節(jié)點,需要把head和當前節(jié)點的后繼連起來迟几,需要變更4個前驅(qū)后繼的引用消请,但是直接把當前節(jié)點設(shè)置成頭結(jié)點,只有三次类腮。


Q:如果處于排隊等候機制中的線程一直無法獲取鎖臊泰,需要一直等待么?還是有別的策略來解決這一問題蚜枢?
A:線程所在節(jié)點的狀態(tài)會變成取消狀態(tài)缸逃,取消狀態(tài)的節(jié)點會從隊列中釋放。


Q:Lock函數(shù)通過Acquire方法進行加鎖厂抽,但是具體是如何加鎖的呢需频?
A:AQS的Acquire會調(diào)用tryAcquire方法,tryAcquire由各個自定義同步器實現(xiàn)修肠,通過tryAcquire完成加鎖過程贺辰。


Q:shouldParkAfterFailedAcquire中取消節(jié)點是怎么生成的呢?什么時候會把一個節(jié)點的waitStatus設(shè)置為-1?
A:是在cancelAcquire中產(chǎn)生的饲化;如果當前節(jié)點不是head的后繼節(jié)點莽鸭,1:判斷當前節(jié)點前驅(qū)節(jié)點的是否為SIGNAL,2:如果不是吃靠,則把前驅(qū)節(jié)點設(shè)置為SINGAL看是否成功


Q:被掛起阻塞的線程是什么時候進行恢復(fù)的硫眨?
A:首先被喚醒調(diào)用的方法是unparkSuccessor(Node node) ;線程喚醒發(fā)生在取消請求時cancelAcquire()巢块,或釋放鎖時對unparkSuccessor()的調(diào)用礁阁。


Q:喚醒節(jié)點的邏輯是從后尾部往前遍歷找到最前的一個處于正常阻塞狀態(tài)的結(jié)點,為什么需要從后往前遍歷
A:如果是從前往后找族奢,由于極端情況下addWaiter入隊的非原子操作和CANCELLED節(jié)點產(chǎn)生過程中斷開Next指針的操作姥闭,可能會導致無法遍歷所有的節(jié)點。
在enq方法插入新節(jié)點時越走,可能存在舊尾節(jié)點的next指針還未指向新節(jié)點的情況;
在shouldParkAfterFailedAcquire方法中棚品,當移除CANCEL狀態(tài)的節(jié)點時,也存在next指針還未指向后續(xù)節(jié)點的情況


Q:對ReentrantLock非公平性的理解
A:首次去獲取鎖及獲取鎖失敗重試都是非公平的廊敌,都會和隊列里面的競爭铜跑,當加入隊列后,相對于整個FIFO隊列的node節(jié)點來說骡澈,又是公平的锅纺,但在隊列里面獲取鎖的時候又會同其他線程競爭,又體現(xiàn)了非公平性


Q:ReentrantLock與synchronized的區(qū)別
A: ReentrantLock具有以下優(yōu)勢

  • ReentrantLock有公平鎖
  • ReentrantLock嘗試加鎖肋殴,獲取鎖帶超時時間
  • ReentrantLock獲取鎖響應(yīng)中斷
  • 底層實現(xiàn):synchronized 是 JVM層面的鎖囤锉,是Java關(guān)鍵字,通過monitor對象來完成(monitorenter與monitorexit)疼电,ReentrantLock 是從jdk1.5以來(java.util.concurrent.locks.Lock)提供的API層面的鎖
  • 鎖的對象:synchronzied 鎖的是對象嚼锄,鎖是保存在對象頭里面的,根據(jù)對象頭數(shù)據(jù)來標識是否有線程獲得鎖/爭搶鎖蔽豺;ReentrantLock 是根據(jù)volatile 變量 state 標識鎖的獲得/爭搶区丑。
  • 實現(xiàn)機制:synchronized 的實現(xiàn)涉及到鎖的升級,具體為無鎖修陡、偏向鎖沧侥、自旋鎖、向內(nèi)核態(tài)申請重量級鎖魄鸦,ReentrantLock實現(xiàn)則是通過利用CAS(CompareAndSwap)自旋機制保證線程操作的原子性和volatile 保證數(shù)據(jù)可見性以實現(xiàn)鎖的功能宴杀。
  • 釋放鎖方式:synchronized 不需要用戶去手動釋放鎖,synchronized 代碼執(zhí)行完后系統(tǒng)會自動釋放鎖拾因;ReentrantLock 需要用戶去手動釋放鎖旺罢,如果沒有手動釋放鎖旷余,就可能導致死鎖現(xiàn)象。一般通過lock() 和unlock() 方法配合 try/finally 語句塊來完成扁达。
  • 帶條件的鎖:synchronized不能綁定條件正卧;ReentrantLock 可以綁定Condition 結(jié)合await()/singal() 方法實現(xiàn)線程的精準喚醒,而不是像synchronized通過Object類的wait()/notify()/notifyAll() 方法要么隨機喚醒一個線程要么喚醒全部線程跪解。

總結(jié):
ReentrantLock 與 synchronized的區(qū)別:

  • synchronized 屬于非公平鎖炉旷,ReentrantLock 支持公平鎖、非公平鎖叉讥;
  • synchronized 不支持嘗試加鎖窘行,ReentrantLock 支持嘗試加鎖,支持帶超時時間的嘗試加鎖图仓;
  • synchronized 不支持響應(yīng)中斷的獲取鎖罐盔,ReentrantLock 提供了響應(yīng)中斷的加鎖方法;
  • synchronized 不支持帶條件救崔,ReentrantLock支持翘骂;
  • 其他底層實現(xiàn)上、實現(xiàn)機制上帚豪、鎖的對象上役听、釋放鎖的方式上也有區(qū)別底洗。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市粉寞,隨后出現(xiàn)的幾起案子昌执,更是在濱河造成了極大的恐慌烛亦,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懂拾,死亡現(xiàn)場離奇詭異煤禽,居然都是意外死亡,警方通過查閱死者的電腦和手機岖赋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門檬果,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人唐断,你說我怎么就攤上這事选脊。” “怎么了脸甘?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵恳啥,是天一觀的道長。 經(jīng)常有香客問我丹诀,道長钝的,這世上最難降的妖魔是什么翁垂? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮硝桩,結(jié)果婚禮上沿猜,老公的妹妹穿的比我還像新娘。我一直安慰自己亿柑,他們只是感情好邢疙,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著望薄,像睡著了一般疟游。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上痕支,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天颁虐,我揣著相機與錄音,去河邊找鬼卧须。 笑死另绩,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的花嘶。 我是一名探鬼主播笋籽,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼椭员!你這毒婦竟也來了车海?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤隘击,失蹤者是張志新(化名)和其女友劉穎侍芝,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體埋同,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡州叠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了凶赁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咧栗。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哟冬,靈堂內(nèi)的尸體忽然破棺而出楼熄,到底是詐尸還是另有隱情,我是刑警寧澤浩峡,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布可岂,位于F島的核電站,受9級特大地震影響翰灾,放射性物質(zhì)發(fā)生泄漏缕粹。R本人自食惡果不足惜稚茅,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望平斩。 院中可真熱鬧亚享,春花似錦、人聲如沸绘面。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揭璃。三九已至晚凿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瘦馍,已是汗流浹背歼秽。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留情组,地道東北人燥筷。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像院崇,于是被迫代替她去往敵國和親肆氓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

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