Java AQS源碼分析

在Java中,如果要對(duì)某一共享資源進(jìn)行互斥訪問(wèn),一般可以通過(guò)使用synchronized關(guān)鍵字或者是java.util.concurrent.locks包下面的某些類來(lái)實(shí)現(xiàn),比如ReentrantLock。這個(gè)包下的所有類幾乎都是以AbstractQueuedSynchronizer(后文中簡(jiǎn)稱AQS)為基礎(chǔ)來(lái)構(gòu)建的,AQS為鎖的實(shí)現(xiàn)提供了一個(gè)基本機(jī)制,包括掛起和喚醒某一線程、自動(dòng)管理同步狀態(tài)以及將獲取不到鎖的線程入隊(duì)等,在這些基礎(chǔ)機(jī)制之上,可以實(shí)現(xiàn)各種特性不一樣的鎖,比如公平鎖,非公平鎖,可被中斷的鎖等等

在基本用法上,synchronized和ReentrantLock很相似,基本用法都差不多,語(yǔ)義也差不多,都具備可重入等特性。synchronized是在字節(jié)碼層面上通過(guò)monitorenter和monitorexit來(lái)實(shí)現(xiàn)的,線程的阻塞(獲取不到鎖時(shí))和喚醒(待獲取的鎖被另一擁有它的線程釋放時(shí))是內(nèi)核自己調(diào)度的,而基于AQS的鎖實(shí)現(xiàn)可以讓實(shí)現(xiàn)者自己決定線程獲取鎖的規(guī)則(當(dāng)某一線程釋放鎖,n多之前因?yàn)闆](méi)有獲取到鎖而阻塞的線程去獲取這個(gè)已經(jīng)釋放的鎖時(shí)),比如公平鎖和非公平鎖,相比于synchronized,ReentrantLock有比較多的高級(jí)特性,有業(yè)務(wù)需求的話也可以通過(guò)實(shí)現(xiàn)AQS來(lái)進(jìn)行擴(kuò)展,可擴(kuò)展性較好

分析AQS之前需要知道CAS(即compare-and-swap,比較并交換)。某些硬件的指令集提供了CAS指令,CAS指令需要三個(gè)操作數(shù),分別是內(nèi)存地址Loc,舊的預(yù)期值oldValue,新的更新值updateValue,CAS指令執(zhí)行時(shí),當(dāng)且僅當(dāng)oldValue等于Loc位置處的值時(shí),將Loc處的值更新為updateValue,否則不更新。這個(gè)CAS操作是一個(gè)原子操作,且比synchronized更輕量。

如果不想使用synchronized來(lái)更新共享變量的值(考慮到性能),可以用CAS+volatile的方式來(lái)保證線程安全,CAS操作保證了原子性,而volatile保證了可見(jiàn)性

AQS里面會(huì)用到一個(gè)比較重要的類,sun.misc.Unsafe,這個(gè)類的大部分方法都是native方法,可以執(zhí)行一些比較“另類”的方法,例如向操作系統(tǒng)申請(qǐng)內(nèi)存,也可以用Unsafe來(lái)執(zhí)行CAS操作,掛起和喚醒線程也是通過(guò)Unsafe的park和unpark來(lái)實(shí)現(xiàn)的

public abstract class AbstractQueuedSynchronizer
        extends AbstractOwnableSynchronizer
        implements java.io.Serializable {

    private transient volatile Node head;           //鏈表的頭指針
    private transient volatile Node tail;           //鏈表的尾指針
    private volatile int state;                     ///同步狀態(tài)

    static final class Node {
        static final Node SHARED = new Node();        //當(dāng)前等待的鎖為共享模式
        static final Node EXCLUSIVE = null;           //當(dāng)前等待的鎖為排他模式

        static final int CANCELLED = 1;
        static final int SIGNAL = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;

        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;

    }
}

可以看到AQS里面有一個(gè)內(nèi)部類Node,Node里面有prev和next引用,因此Node可以構(gòu)成一個(gè)雙向鏈表,并且Node里面還有一個(gè)Thread的引用,可以猜想,每一個(gè)打算去獲取鎖的線程在AQS里面都會(huì)先包裝成一個(gè)Node,假如獲取不到鎖的話,會(huì)將當(dāng)前的Node加入這個(gè)鏈表,當(dāng)某一個(gè)線程釋放鎖的時(shí)候,可以從鏈表里面取一個(gè)正在等待的Node,并將鎖給他

首先來(lái)看看獲取鎖的方法

public final void acquire(int arg){
        if(!tryAcquire(arg)&&
        acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
        selfInterrupt();
        }

整個(gè)方法的邏輯為:首先嘗試去獲取鎖,如果由于競(jìng)爭(zhēng)失敗導(dǎo)致獲取鎖失敗,則將自身包裝成一個(gè)node對(duì)象加入到鏈表末尾,并嘗試去獲取鎖

其中tryAcquire(arg)為一個(gè)protected方法,需要子類去實(shí)現(xiàn),這個(gè)方法試圖去獲取鎖,如果成功獲取到鎖,則返回true,否則返回false。等下次分析某個(gè)AQS的實(shí)現(xiàn)類時(shí),可以看到tryAcquire(arg)的一些具體實(shí)現(xiàn)(會(huì)涉及到同步狀態(tài)state),但是現(xiàn)在可以不用管它,只需要知道,如果獲取到鎖了就返回true,沒(méi)獲取到鎖就返回false

如果沒(méi)有獲取到鎖,則進(jìn)入addWaiter(Node mode)方法
本文中,說(shuō)到步驟的時(shí)候都是以 “方法名.步驟n” 這樣一種形式來(lái)描述,以避免沖突,假如方法是當(dāng)前正在分析的方法,則省略方法名,只說(shuō)步驟n

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode):                      //1
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {                                                      //2
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {                                 //3
                pred.next = node;
                return node;
            }
        }
        enq(node);                                                               //4
        return node;                                                             //5
    }

這個(gè)方法的步驟為:
1:首先利用當(dāng)前線程創(chuàng)建node節(jié)點(diǎn),并取出尾節(jié)點(diǎn)tail
2:判斷之前的尾節(jié)點(diǎn)tail是否為null。如果不為null,則將新創(chuàng)建節(jié)點(diǎn)node的prev引用指向尾節(jié)點(diǎn)tail,并進(jìn)入步驟3。如果尾節(jié)點(diǎn)為null,則進(jìn)入步驟4
3:這時(shí)候利用CAS操作試圖將尾節(jié)點(diǎn)修改為新創(chuàng)建的node節(jié)點(diǎn)。如果CAS操作成功,則新創(chuàng)建的節(jié)點(diǎn)成為尾節(jié)點(diǎn),并且將之前尾節(jié)點(diǎn)的next引用指向新創(chuàng)建的節(jié)點(diǎn)node,實(shí)現(xiàn)一個(gè)鏈表中兩個(gè)節(jié)點(diǎn)的連接行為,并返回這個(gè)新創(chuàng)建的節(jié)點(diǎn)node。如果CAS操作失敗,則說(shuō)明已經(jīng)有其它線程搶先一步將它自己加入到了鏈表中,此時(shí),進(jìn)入步驟4
4:如果尾節(jié)點(diǎn)為null或者步驟3中的CAS操作失敗,則會(huì)進(jìn)入此步驟,注意步驟4中傳入的參數(shù)為新創(chuàng)建的node節(jié)點(diǎn)

接下來(lái)看下步驟4中的enq(Node node)方法,注意,enq方法是用final修飾的

private Node enq(final Node node) {
        for (; ; ) {
            Node t = tail;                                  //1
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))          //2
                    tail = head;
            } else {
                node.prev = t;                              //3
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

此方法中,for是一個(gè)無(wú)線循環(huán),出口只有else分支里面的一個(gè)return語(yǔ)句,此方法的步驟為:
1:首先取出尾節(jié)點(diǎn)tail,并判斷尾節(jié)點(diǎn)是否為null。如果尾節(jié)點(diǎn)為null,則進(jìn)入步驟2,否則進(jìn)入步驟3
2:新創(chuàng)建一個(gè)節(jié)點(diǎn)(這個(gè)節(jié)點(diǎn)并沒(méi)有綁定某一個(gè)線程,可以認(rèn)為是一個(gè)“空”節(jié)點(diǎn)),并通過(guò)CAS操作嘗試將其設(shè)置為頭結(jié)點(diǎn),如果設(shè)置成功,將尾節(jié)點(diǎn)也指向剛創(chuàng)建的節(jié)點(diǎn),并進(jìn)入下一次循環(huán),此時(shí)tail節(jié)點(diǎn)已經(jīng)不為null。需要注意的是這里并沒(méi)有采用CAS的方式將新創(chuàng)建的節(jié)點(diǎn)設(shè)置為tail,這是不必要的,因?yàn)閞eturn語(yǔ)句在else分支里面,而進(jìn)入else分支的條件是尾節(jié)點(diǎn)不為null,假如我們?cè)趖ail = head處打一個(gè)斷點(diǎn),并且讓當(dāng)前線程跑到這個(gè)斷點(diǎn)處,這時(shí)候讓其它的線程調(diào)用acquire方法,當(dāng)其他的線程進(jìn)入enq方法后,由于頭結(jié)點(diǎn)已經(jīng)被設(shè)置了,所以其他線程在//2處的CAS操作將失敗,此時(shí)會(huì)進(jìn)行下一次循環(huán),從這可以看到,初始化的時(shí)候,假如A線程設(shè)置了head節(jié)點(diǎn),那就只有A線程有權(quán)利更新尾節(jié)點(diǎn)tail,之后方法才能正常進(jìn)行下去。而只有設(shè)置了tail節(jié)點(diǎn)之后,代碼才有可能走到else分支從而返回。
3:如果尾節(jié)點(diǎn)tail存在,則將傳入?yún)?shù)node的prev指針指向tail,并利用CAS操作嘗試將傳入的節(jié)點(diǎn)node設(shè)置為尾節(jié)點(diǎn),如果CAS操作成功,則將之前尾節(jié)點(diǎn)的next指針指向傳入的參數(shù)node,并返回之前的尾節(jié)點(diǎn)tail,如果CAS操作失敗(其它線程搶先一步將其自己設(shè)置成了尾節(jié)點(diǎn)),則進(jìn)入下一次循環(huán)并繼續(xù)嘗試將傳入的參數(shù)節(jié)點(diǎn)node設(shè)置為尾節(jié)點(diǎn),直到CAS操作在某次重試中成功

當(dāng)enq(Node node)方法成功返回后,進(jìn)入步驟addWaiter.5,返回新創(chuàng)建的node

所以addWaiter()要做的就是,在多線程的環(huán)境下,競(jìng)爭(zhēng)鎖失敗的時(shí)候,將當(dāng)前線程包裝成一個(gè)node節(jié)點(diǎn),并將其安全地插入到鏈表的末尾。
需要注意的是,假如之前一個(gè)線程已經(jīng)獲得了某一個(gè)鎖且未釋放,也沒(méi)有其它線程請(qǐng)求去獲取鎖,這時(shí)候鏈表中并沒(méi)有任何節(jié)點(diǎn)元素,這時(shí)候假如有另一個(gè)線程去請(qǐng)求獲取這個(gè)鎖,則會(huì)將當(dāng)前線程封裝的節(jié)點(diǎn)node插入鏈表末尾,這個(gè)節(jié)點(diǎn)在鏈表中的位置并不是頭結(jié)點(diǎn),而是鏈表中的第二個(gè)節(jié)點(diǎn)。初始化鏈表的時(shí)候會(huì)插入一個(gè)不綁定線程的“空”節(jié)點(diǎn)
接下來(lái)調(diào)用acquireQueued方法

acquireQueued(final Node node, int arg)方法如下所示,可以看到,只有在當(dāng)前節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)是頭結(jié)點(diǎn)head的時(shí)候才有可能獲取到鎖(即當(dāng)前節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)為head頭結(jié)點(diǎn)是當(dāng)前節(jié)點(diǎn)能獲取到鎖的前提條件,考慮到非公平鎖,這么說(shuō)其實(shí)有點(diǎn)不太正確)

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;                                   //1
        try {
            boolean interrupted = false;
            for (; ; ) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {              //2
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&     //3
                        parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {                                              //4
            if (failed)
                cancelAcquire(node);
        }
    }

該方法的步驟為:
1:首先將failed初始化為true,表示獲取鎖是否失敗,失敗了會(huì)在步驟4中做一些后續(xù)工作,至于interrupted的含義,會(huì)在后文看到。然后取出當(dāng)前節(jié)點(diǎn)node的前一個(gè)節(jié)點(diǎn)p 。如果p為頭結(jié)點(diǎn)head且當(dāng)前線程能獲取到鎖,則進(jìn)入步驟2。否則,如果不是頭結(jié)點(diǎn)或者獲取不到鎖,進(jìn)入步驟3
2:當(dāng)當(dāng)前節(jié)點(diǎn)node的prev為head頭結(jié)點(diǎn),且能獲取到鎖時(shí),進(jìn)入該步驟。將當(dāng)前節(jié)點(diǎn)設(shè)為頭結(jié)點(diǎn),并且將之前的head頭結(jié)點(diǎn)從鏈表中分離,這時(shí)候?qū)ailed設(shè)置為false,表示成功獲取到了鎖。并且返回interrupted。注意,這里設(shè)置頭結(jié)點(diǎn)head的時(shí)候并不需要通過(guò)CAS操作來(lái)進(jìn)行,因?yàn)闆](méi)有競(jìng)爭(zhēng)
3:如果當(dāng)前節(jié)點(diǎn)node的前一個(gè)節(jié)點(diǎn)不為head頭節(jié)點(diǎn),或者沒(méi)有獲取到鎖,會(huì)進(jìn)入該步驟。該步驟會(huì)調(diào)用shouldParkAfterFailedAcquire(Node pred, Node node)方法,這個(gè)方法的功能已經(jīng)被方法名描述的很形象了,就是判斷鎖獲取失敗之后需不需要將當(dāng)前線程掛起(AQS中通過(guò)park和unpark方法來(lái)掛起和喚醒線程),假如shouldParkAfterFailedAcquire返回true,則調(diào)用parkAndCheckInterrupt方法,如果parkAndCheckInterrupt也返回true,則將interrupted賦值為true

首先看看shouldParkAfterFailedAcquire方法

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)                                   //1
            return true;
        if (ws > 0) {                                            //2
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {                                                 //3
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

先說(shuō)一下內(nèi)部類Node的waitStatus字段以及CANCELLED、SIGNAL、CONDITION、PROPAGATE等字段,在源碼里有關(guān)于這幾個(gè)字段的簡(jiǎn)短說(shuō)明

/** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;

大致意思就是說(shuō),waitStatus表示了當(dāng)前節(jié)點(diǎn)所代表線程所在的狀態(tài),有可能為CANCELLED、SIGNAL、CONDITION、PROPAGATE、0中其中的一種
CANCELLED表示當(dāng)前線程由于超時(shí)或者被中斷
SIGNAL表示當(dāng)前節(jié)點(diǎn)的next指向的節(jié)點(diǎn)正在阻塞或即將被阻塞,所以當(dāng)前節(jié)點(diǎn)釋放鎖或者取消時(shí)要喚醒其后一個(gè)節(jié)點(diǎn)(也就意味著假如當(dāng)前節(jié)點(diǎn)的waitStatus為SIGNAL,當(dāng)它釋放鎖或者取消時(shí)會(huì)去喚醒它的下一個(gè)節(jié)點(diǎn))
CONDITION:表示當(dāng)前節(jié)點(diǎn)處于條件隊(duì)列中
PROPAGATE:表示喚醒當(dāng)前節(jié)點(diǎn)的動(dòng)作需要向后傳播(不僅僅限于當(dāng)前節(jié)點(diǎn)的后一個(gè)節(jié)點(diǎn)),在某些共享鎖里面會(huì)用到這個(gè),排它鎖不會(huì)用到這個(gè)值

1:如果當(dāng)前節(jié)點(diǎn)node的前一個(gè)節(jié)點(diǎn)pred的waitStatus為SIGNAL,則返回true(如果pred節(jié)點(diǎn)的waitStatus為SIGNAL,則意味著當(dāng)它釋放鎖或者取消時(shí)會(huì)去喚醒它的下一個(gè)節(jié)點(diǎn)node,所以這時(shí)候我們只需要將node所代表的線程掛起就行了,所以返回true)
2:如果當(dāng)前節(jié)點(diǎn)node的前一個(gè)節(jié)點(diǎn)的waitStatus大于0,會(huì)將node之前的waitStatus大于0的所有節(jié)點(diǎn)從鏈表中刪除掉,直到某一個(gè)節(jié)點(diǎn)的waitStatus小于等于0,并且將這個(gè)節(jié)點(diǎn)和node互相關(guān)聯(lián)起來(lái)
3:如果當(dāng)前節(jié)點(diǎn)node的前一個(gè)節(jié)點(diǎn)pred的waitStatus等于0,則嘗試用CAS操作將pred節(jié)點(diǎn)的waitStatus值改為Node.SIGNAL
在分支2和分支3中,由于其前面一個(gè)節(jié)點(diǎn)的waitStatus不為SIGNAL,所以沒(méi)辦法保證會(huì)有一個(gè)線程去喚醒node,則只能返回false且進(jìn)入下一次循環(huán)(即只有在前一個(gè)節(jié)點(diǎn)的waitStatus為SIGNAL的情況下,后面的線程才有可能被喚醒,即掛起node所代表的線程是安全的)
如果shouldParkAfterFailedAcquire方法返回true,則進(jìn)入parkAndCheckInterrupt()方法

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

可以看到parkAndCheckInterrupt()方法很簡(jiǎn)單,只是掛起當(dāng)前線程并返回當(dāng)前線程的中斷標(biāo)記(如果有其他線程喚醒它)
如果當(dāng)前線程被設(shè)置過(guò)中斷標(biāo)記(注意,interrupted方法會(huì)清除掉線程的中斷狀態(tài))
則步驟acquireQueued.3中會(huì)將interrupted置為true并返回,以標(biāo)志當(dāng)前線程在等待鎖的過(guò)程中是否被中斷過(guò)
當(dāng)當(dāng)前線程被喚醒后,又會(huì)進(jìn)入下一次循環(huán),繼續(xù)下一次獲取鎖的嘗試,直到其獲取到鎖或者拋出異常
當(dāng)acquireQueued返回true時(shí),acquire方法的selfInterrupt方法會(huì)設(shè)置當(dāng)前線程的中斷標(biāo)記

整個(gè)acquire方法的精髓就在這兒了,一個(gè)節(jié)點(diǎn)node的前一個(gè)節(jié)點(diǎn)假如是head節(jié)點(diǎn),他才會(huì)去嘗試獲取鎖,需要注意的是,這里不一定能夠獲取到鎖(非公平鎖),假如獲取不到鎖,線程會(huì)嘗試將自己掛起,等待被中斷或者被其它的線程喚醒,當(dāng)被喚醒后,會(huì)繼續(xù)嘗試去獲取鎖,直到獲取到鎖或者拋出異常,又或者被取消等等。。

接下來(lái)說(shuō)下線程釋放鎖的過(guò)程:

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        if (s == null || s.waitStatus > 0) {                                       //1
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

在unparkSuccessor方法中,如果node的waitStatus為0,首先會(huì)嘗試將node的waitStatus設(shè)置為0,就算CAS操作失敗了也不要緊,這意味著node的waitStatus已經(jīng)被改變了。
在步驟1中,如果當(dāng)前節(jié)點(diǎn)node的后一個(gè)節(jié)點(diǎn)s不為null且s的waitStatus小于等于0,則可以直接喚醒s對(duì)應(yīng)的線程,如果s為null或者s的waitStatus大于0,則會(huì)從鏈表的末尾開(kāi)始向前找,直到找到一個(gè)距離節(jié)點(diǎn)node最近的且waitStatus小于等于0的節(jié)點(diǎn),并喚醒它

Doug Lea大師把CAS指令玩的飛起。只能膜拜!!

待續(xù)...

key words:AQS CAS Unsafe

關(guān)于AQS,多線程大師Doug Lea發(fā)表了一篇論文,《The java.util.concurrent Synchronizer Framework》,感興趣的可以看看

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,497評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,305評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,962評(píng)論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,727評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,193評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,411評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,945評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,777評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,978評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,216評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,657評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,960評(píng)論 2 373

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