【死磕Java并發(fā)】-----J.U.C之讀寫鎖:ReentrantReadWriteLock

此篇博客所有源碼均來自JDK 1.8

重入鎖ReentrantLock是排他鎖乱豆,排他鎖在同一時(shí)刻僅有一個(gè)線程可以進(jìn)行訪問茎芋,但是在大多數(shù)場景下遂庄,大部分時(shí)間都是提供讀服務(wù)乒融,而寫服務(wù)占有的時(shí)間較少古戴。然而讀服務(wù)不存在數(shù)據(jù)競爭問題欠橘,如果一個(gè)線程在讀時(shí)禁止其他線程讀勢必會導(dǎo)致性能降低。所以就提供了讀寫鎖现恼。

讀寫鎖維護(hù)著一對鎖肃续,一個(gè)讀鎖和一個(gè)寫鎖黍檩。通過分離讀鎖和寫鎖,使得并發(fā)性比一般的排他鎖有了較大的提升:在同一時(shí)間可以允許多個(gè)讀線程同時(shí)訪問始锚,但是在寫線程訪問時(shí)刽酱,所有讀線程和寫線程都會被阻塞。

讀寫鎖的主要特性:

  1. 公平性:支持公平性和非公平性瞧捌。
  2. 重入性:支持重入棵里。讀寫鎖最多支持65535個(gè)遞歸寫入鎖和65535個(gè)遞歸讀取鎖。
  3. 鎖降級:遵循獲取寫鎖姐呐、獲取讀鎖在釋放寫鎖的次序殿怜,寫鎖能夠降級成為讀鎖

讀寫鎖ReentrantReadWriteLock實(shí)現(xiàn)接口ReadWriteLock,該接口維護(hù)了一對相關(guān)的鎖曙砂,一個(gè)用于只讀操作稳捆,另一個(gè)用于寫入操作。只要沒有 writer麦轰,讀取鎖可以由多個(gè) reader 線程同時(shí)保持。寫入鎖是獨(dú)占的砖织。

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

ReadWriteLock定義了兩個(gè)方法款侵。readLock()返回用于讀操作的鎖,writeLock()返回用于寫操作的鎖侧纯。ReentrantReadWriteLock定義如下:

    /** 內(nèi)部類  讀鎖 */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** 內(nèi)部類  寫鎖 */
    private final ReentrantReadWriteLock.WriteLock writerLock;

    final Sync sync;

    /** 使用默認(rèn)(非公平)的排序?qū)傩詣?chuàng)建一個(gè)新的 ReentrantReadWriteLock */
    public ReentrantReadWriteLock() {
        this(false);
    }

    /** 使用給定的公平策略創(chuàng)建一個(gè)新的 ReentrantReadWriteLock */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    /** 返回用于寫入操作的鎖 */
    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    /** 返回用于讀取操作的鎖 */
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

 abstract static class Sync extends AbstractQueuedSynchronizer {
        /**
         * 省略其余源代碼
         */
    }

    public static class WriteLock implements Lock, java.io.Serializable{
        /**
         * 省略其余源代碼
         */
    }

    public static class ReadLock implements Lock, java.io.Serializable {
        /**
         * 省略其余源代碼
         */
    }

ReentrantReadWriteLock與ReentrantLock一樣新锈,其鎖主體依然是Sync,它的讀鎖眶熬、寫鎖都是依靠Sync來實(shí)現(xiàn)的妹笆。所以ReentrantReadWriteLock實(shí)際上只有一個(gè)鎖,只是在獲取讀取鎖和寫入鎖的方式上不一樣而已娜氏,它的讀寫鎖其實(shí)就是兩個(gè)類:ReadLock拳缠、writeLock,這兩個(gè)類都是lock實(shí)現(xiàn)贸弥。

在ReentrantLock中使用一個(gè)int類型的state來表示同步狀態(tài)窟坐,該值表示鎖被一個(gè)線程重復(fù)獲取的次數(shù)。但是讀寫鎖ReentrantReadWriteLock內(nèi)部維護(hù)著兩個(gè)一對鎖绵疲,需要用一個(gè)變量維護(hù)多種狀態(tài)哲鸳。所以讀寫鎖采用“按位切割使用”的方式來維護(hù)這個(gè)變量,將其切分為兩部分盔憨,高16為表示讀徙菠,低16為表示寫。分割之后郁岩,讀寫鎖是如何迅速確定讀鎖和寫鎖的狀態(tài)呢婿奔?通過為運(yùn)算缺狠。假如當(dāng)前同步狀態(tài)為S,那么寫狀態(tài)等于 S & 0x0000FFFF(將高16位全部抹去)脸秽,讀狀態(tài)等于S >>> 16(無符號補(bǔ)0右移16位)儒老。代碼如下:

        static final int SHARED_SHIFT   = 16;
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

寫鎖

寫鎖就是一個(gè)支持可重入的排他鎖。

寫鎖的獲取

寫鎖的獲取最終會調(diào)用tryAcquire(int arg)记餐,該方法在內(nèi)部類Sync中實(shí)現(xiàn):

    protected final boolean tryAcquire(int acquires) {
        Thread current = Thread.currentThread();
        //當(dāng)前鎖個(gè)數(shù)
        int c = getState();
        //寫鎖
        int w = exclusiveCount(c);
        if (c != 0) {
            //c != 0 && w == 0 表示存在讀鎖
            //當(dāng)前線程不是已經(jīng)獲取寫鎖的線程
            if (w == 0 || current != getExclusiveOwnerThread())
                return false;
            //超出最大范圍
            if (w + exclusiveCount(acquires) > MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            setState(c + acquires);
            return true;
        }
        //是否需要阻塞
        if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
            return false;
        //設(shè)置獲取鎖的線程為當(dāng)前線程
        setExclusiveOwnerThread(current);
        return true;
    }

該方法和ReentrantLock的tryAcquire(int arg)大致一樣驮樊,在判斷重入時(shí)增加了一項(xiàng)條件:讀鎖是否存在。因?yàn)橐_保寫鎖的操作對讀鎖是可見的片酝,如果在存在讀鎖的情況下允許獲取寫鎖囚衔,那么那些已經(jīng)獲取讀鎖的其他線程可能就無法感知當(dāng)前寫線程的操作。因此只有等讀鎖完全釋放后雕沿,寫鎖才能夠被當(dāng)前線程所獲取练湿,一旦寫鎖獲取了,所有其他讀审轮、寫線程均會被阻塞肥哎。

寫鎖的釋放

獲取了寫鎖用完了則需要釋放,WriteLock提供了unlock()方法釋放寫鎖:

    public void unlock() {
        sync.release(1);
    }

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

寫鎖的釋放最終還是會調(diào)用AQS的模板方法release(int arg)方法疾渣,該方法首先調(diào)用tryRelease(int arg)方法嘗試釋放鎖篡诽,tryRelease(int arg)方法為讀寫鎖內(nèi)部類Sync中定義了,如下:

    protected final boolean tryRelease(int releases) {
        //釋放的線程不為鎖的持有者
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        int nextc = getState() - releases;
        //若寫鎖的新線程數(shù)為0榴捡,則將鎖的持有者設(shè)置為null
        boolean free = exclusiveCount(nextc) == 0;
        if (free)
            setExclusiveOwnerThread(null);
        setState(nextc);
        return free;
    }

寫鎖釋放鎖的整個(gè)過程和獨(dú)占鎖ReentrantLock相似杈女,每次釋放均是減少寫狀態(tài),當(dāng)寫狀態(tài)為0時(shí)表示 寫鎖已經(jīng)完全釋放了吊圾,從而等待的其他線程可以繼續(xù)訪問讀寫鎖达椰,獲取同步狀態(tài),同時(shí)此次寫線程的修改對后續(xù)的線程可見项乒。

讀鎖

讀鎖為一個(gè)可重入的共享鎖啰劲,它能夠被多個(gè)線程同時(shí)持有,在沒有其他寫線程訪問時(shí)檀何,讀鎖總是或獲取成功呈枉。

讀鎖的獲取

讀鎖的獲取可以通過ReadLock的lock()方法:

        public void lock() {
            sync.acquireShared(1);
        }

Sync的acquireShared(int arg)定義在AQS中:

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

tryAcqurireShared(int arg)嘗試獲取讀同步狀態(tài),該方法主要用于獲取共享式同步狀態(tài)埃碱,獲取成功返回 >= 0的返回結(jié)果猖辫,否則返回 < 0 的返回結(jié)果。

    protected final int tryAcquireShared(int unused) {
        //當(dāng)前線程
        Thread current = Thread.currentThread();
        int c = getState();
        //exclusiveCount(c)計(jì)算寫鎖
        //如果存在寫鎖砚殿,且鎖的持有者不是當(dāng)前線程啃憎,直接返回-1
        //存在鎖降級問題,后續(xù)闡述
        if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
            return -1;
        //讀鎖
        int r = sharedCount(c);

        /*
         * readerShouldBlock():讀鎖是否需要等待(公平鎖原則)
         * r < MAX_COUNT:持有線程小于最大數(shù)(65535)
         * compareAndSetState(c, c + SHARED_UNIT):設(shè)置讀取鎖狀態(tài)
         */
        if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
            /*
             * holdCount部分后面講解
             */
            if (r == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    cachedHoldCounter = rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
            }
            return 1;
        }
        return fullTryAcquireShared(current);
    }

**```**
****
讀鎖獲取的過程相對于獨(dú)占鎖而言會稍微復(fù)雜下似炎,整個(gè)過程如下:

1. 因?yàn)榇嬖阪i降級情況辛萍,如果存在寫鎖且鎖的持有者不是當(dāng)前線程則直接返回失敗悯姊,否則繼續(xù)
2. 依據(jù)公平性原則,判斷讀鎖是否需要阻塞贩毕,讀鎖持有線程數(shù)小于最大值(65535)悯许,且設(shè)置鎖狀態(tài)成功,執(zhí)行以下代碼(對于HoldCounter下面再闡述)辉阶,并返回1先壕。如果不滿足改條件,執(zhí)行fullTryAcquireShared()谆甜。

```java
    final int fullTryAcquireShared(Thread current) {
        HoldCounter rh = null;
        for (;;) {
            int c = getState();
            //鎖降級
            if (exclusiveCount(c) != 0) {
                if (getExclusiveOwnerThread() != current)
                    return -1;
            }
            //讀鎖需要阻塞
            else if (readerShouldBlock()) {
                //列頭為當(dāng)前線程
                if (firstReader == current) {
                }
                //HoldCounter后面講解
                else {
                    if (rh == null) {
                        rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current)) {
                            rh = readHolds.get();
                            if (rh.count == 0)
                                readHolds.remove();
                        }
                    }
                    if (rh.count == 0)
                        return -1;
                }
            }
            //讀鎖超出最大范圍
            if (sharedCount(c) == MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            //CAS設(shè)置讀鎖成功
            if (compareAndSetState(c, c + SHARED_UNIT)) {
                //如果是第1次獲取“讀取鎖”垃僚,則更新firstReader和firstReaderHoldCount
                if (sharedCount(c) == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                }
                //如果想要獲取鎖的線程(current)是第1個(gè)獲取鎖(firstReader)的線程,則將firstReaderHoldCount+1
                else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    if (rh == null)
                        rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    //更新線程的獲取“讀取鎖”的共享計(jì)數(shù)
                    rh.count++;
                    cachedHoldCounter = rh; // cache for release
                }
                return 1;
            }
        }
    }

fullTryAcquireShared(Thread current)會根據(jù)“是否需要阻塞等待”,“讀取鎖的共享計(jì)數(shù)是否超過限制”等等進(jìn)行處理规辱。如果不需要阻塞等待谆棺,并且鎖的共享計(jì)數(shù)沒有超過限制,則通過CAS嘗試獲取鎖罕袋,并返回1

讀鎖的釋放

與寫鎖相同改淑,讀鎖也提供了unlock()釋放讀鎖:

        public void unlock() {
            sync.releaseShared(1);
        }

unlcok()方法內(nèi)部使用Sync的releaseShared(int arg)方法,該方法定義在AQS中:

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

調(diào)用tryReleaseShared(int arg)嘗試釋放讀鎖,該方法定義在讀寫鎖的Sync內(nèi)部類中:

    protected final boolean tryReleaseShared(int unused) {
        Thread current = Thread.currentThread();
        //如果想要釋放鎖的線程為第一個(gè)獲取鎖的線程
        if (firstReader == current) {
            //僅獲取了一次,則需要將firstReader 設(shè)置null名秀,否則 firstReaderHoldCount - 1
            if (firstReaderHoldCount == 1)
                firstReader = null;
            else
                firstReaderHoldCount--;
        }
        //獲取rh對象,并更新“當(dāng)前線程獲取鎖的信息”
        else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                rh = readHolds.get();
            int count = rh.count;
            if (count <= 1) {
                readHolds.remove();
                if (count <= 0)
                    throw unmatchedUnlockException();
            }
            --rh.count;
        }
        //CAS更新同步狀態(tài)
        for (;;) {
            int c = getState();
            int nextc = c - SHARED_UNIT;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }

HoldCounter

在讀鎖獲取鎖和釋放鎖的過程中,我們一直都可以看到一個(gè)變量rh (HoldCounter )询吴,該變量在讀鎖中扮演著非常重要的作用掠河。

我們了解讀鎖的內(nèi)在機(jī)制其實(shí)就是一個(gè)共享鎖,為了更好理解HoldCounter 猛计,我們暫且認(rèn)為它不是一個(gè)鎖的概率唠摹,而相當(dāng)于一個(gè)計(jì)數(shù)器。一次共享鎖的操作就相當(dāng)于在該計(jì)數(shù)器的操作奉瘤。獲取共享鎖勾拉,則該計(jì)數(shù)器 + 1,釋放共享鎖盗温,該計(jì)數(shù)器 - 1藕赞。只有當(dāng)線程獲取共享鎖后才能對共享鎖進(jìn)行釋放、重入操作卖局。所以HoldCounter的作用就是當(dāng)前線程持有共享鎖的數(shù)量斧蜕,這個(gè)數(shù)量必須要與線程綁定在一起,否則操作其他線程鎖就會拋出異常砚偶。我們先看HoldCounter的定義:

        static final class HoldCounter {
            int count = 0;
            final long tid = getThreadId(Thread.currentThread());
        }

HoldCounter 定義非常簡單批销,就是一個(gè)計(jì)數(shù)器count 和線程 id tid 兩個(gè)變量洒闸。按照這個(gè)意思我們看到HoldCounter 是需要和某給線程進(jìn)行綁定了,我們知道如果要將一個(gè)對象和線程綁定僅僅有tid是不夠的均芽,而且從上面的代碼我們可以看到HoldCounter 僅僅只是記錄了tid丘逸,根本起不到綁定線程的作用。那么怎么實(shí)現(xiàn)呢掀宋?答案是ThreadLocal深纲,定義如下:

        static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }

通過上面代碼HoldCounter就可以與線程進(jìn)行綁定了。故而布朦,HoldCounter應(yīng)該就是綁定線程上的一個(gè)計(jì)數(shù)器囤萤,而ThradLocalHoldCounter則是線程綁定的ThreadLocal。從上面我們可以看到ThreadLocal將HoldCounter綁定到當(dāng)前線程上是趴,同時(shí)HoldCounter也持有線程Id涛舍,這樣在釋放鎖的時(shí)候才能知道ReadWriteLock里面緩存的上一個(gè)讀取線程(cachedHoldCounter)是否是當(dāng)前線程。這樣做的好處是可以減少ThreadLocal.get()的次數(shù)唆途,因?yàn)檫@也是一個(gè)耗時(shí)操作富雅。需要說明的是這樣HoldCounter綁定線程id而不綁定線程對象的原因是避免HoldCounter和ThreadLocal互相綁定而GC難以釋放它們(盡管GC能夠智能的發(fā)現(xiàn)這種引用而回收它們,但是這需要一定的代價(jià))肛搬,所以其實(shí)這樣做只是為了幫助GC快速回收對象而已没佑。

看到這里我們明白了HoldCounter作用了,我們在看一個(gè)獲取讀鎖的代碼段:


                else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    if (rh == null)
                        rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                    cachedHoldCounter = rh; // cache for release
                }

這段代碼涉及了幾個(gè)變量:firstReader 温赔、firstReaderHoldCount蛤奢、cachedHoldCounter 。我們先理清楚這幾個(gè)變量:

private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
private transient HoldCounter cachedHoldCounter;

firstReader 看名字就明白了為第一個(gè)獲取讀鎖的線程陶贼,firstReaderHoldCount為第一個(gè)獲取讀鎖的重入數(shù)啤贩,cachedHoldCounter為HoldCounter的緩存。

理清楚上面所有的變量了拜秧,HoldCounter也明白了痹屹,我們就來給上面那段代碼標(biāo)明注釋,如下:

    //如果獲取讀鎖的線程為第一次獲取讀鎖的線程枉氮,則firstReaderHoldCount重入數(shù) + 1
    else if (firstReader == current) {
        firstReaderHoldCount++;
    } else {
        //非firstReader計(jì)數(shù)
        if (rh == null)
            rh = cachedHoldCounter;
        //rh == null 或者 rh.tid != current.getId()志衍,需要獲取rh
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
            //加入到readHolds中
        else if (rh.count == 0)
            readHolds.set(rh);
        //計(jì)數(shù)+1
        rh.count++;
        cachedHoldCounter = rh; // cache for release
    }

這里解釋下為何要引入firstRead、firstReaderHoldCount聊替。這是為了一個(gè)效率問題楼肪,firstReader是不會放入到readHolds中的,如果讀鎖僅有一個(gè)的情況下就會避免查找readHolds惹悄。

鎖降級

上開篇是LZ就闡述了讀寫鎖有一個(gè)特性就是鎖降級淹辞,鎖降級就意味著寫鎖是可以降級為讀鎖的,但是需要遵循先獲取寫鎖、獲取讀鎖在釋放寫鎖的次序象缀。注意如果當(dāng)前線程先獲取寫鎖蔬将,然后釋放寫鎖,再獲取讀鎖這個(gè)過程不能稱之為鎖降級央星,鎖降級一定要遵循那個(gè)次序霞怀。

在獲取讀鎖的方法tryAcquireShared(int unused)中,有一段代碼就是來判讀鎖降級的:

        int c = getState();
        //exclusiveCount(c)計(jì)算寫鎖
        //如果存在寫鎖莉给,且鎖的持有者不是當(dāng)前線程毙石,直接返回-1
        //存在鎖降級問題,后續(xù)闡述
        if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
            return -1;
        //讀鎖
        int r = sharedCount(c);

鎖降級中讀鎖的獲取釋放為必要颓遏?肯定是必要的徐矩。試想,假如當(dāng)前線程A不獲取讀鎖而是直接釋放了寫鎖叁幢,這個(gè)時(shí)候另外一個(gè)線程B獲取了寫鎖滤灯,那么這個(gè)線程B對數(shù)據(jù)的修改是不會對當(dāng)前線程A可見的。如果獲取了讀鎖曼玩,則線程B在獲取寫鎖過程中判斷如果有讀鎖還沒有釋放則會被阻塞鳞骤,只有當(dāng)前線程A釋放讀鎖后,線程B才會獲取寫鎖成功黍判。

推薦閱讀

因?yàn)槔锩婧芏嗟胤缴婕暗搅薃QS部分豫尽,推薦閱讀如下部分:

  1. 【死磕Java并發(fā)】-----J.U.C之AQS:AQS簡介
  2. 【死磕Java并發(fā)】-----J.U.C之AQS:CLH同步隊(duì)列
  3. 【死磕Java并發(fā)】-----J.U.C之AQS:同步狀態(tài)的獲取與釋放
  4. 【死磕Java并發(fā)】-----J.U.C之AQS:阻塞和喚醒線程

參考資料

  1. Doug Lea:《Java并發(fā)編程實(shí)戰(zhàn)》
  2. 方騰飛:《Java并發(fā)編程的藝術(shù)》
  3. 【Java并發(fā)編程實(shí)戰(zhàn)】—–“J.U.C”:ReentrantReadWriteLock](http://cmsblogs.com/?p=1679))
  4. Java多線程(十)之ReentrantReadWriteLock深入分析
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市顷帖,隨后出現(xiàn)的幾起案子美旧,更是在濱河造成了極大的恐慌,老刑警劉巖贬墩,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榴嗅,死亡現(xiàn)場離奇詭異,居然都是意外死亡震糖,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門趴腋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吊说,“玉大人,你說我怎么就攤上這事优炬“渚” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵蠢护,是天一觀的道長雅宾。 經(jīng)常有香客問我,道長葵硕,這世上最難降的妖魔是什么眉抬? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任贯吓,我火速辦了婚禮,結(jié)果婚禮上蜀变,老公的妹妹穿的比我還像新娘悄谐。我一直安慰自己,他們只是感情好库北,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布爬舰。 她就那樣靜靜地躺著,像睡著了一般寒瓦。 火紅的嫁衣襯著肌膚如雪情屹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天杂腰,我揣著相機(jī)與錄音垃你,去河邊找鬼。 笑死颈墅,一個(gè)胖子當(dāng)著我的面吹牛蜡镶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播恤筛,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼官还,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了毒坛?” 一聲冷哼從身側(cè)響起望伦,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎煎殷,沒想到半個(gè)月后屯伞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡豪直,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年劣摇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弓乙。...
    茶點(diǎn)故事閱讀 39,703評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡末融,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出暇韧,到底是詐尸還是另有隱情勾习,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布懈玻,位于F島的核電站巧婶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜艺栈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一英岭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧眼滤,春花似錦巴席、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至堰塌,卻和暖如春赵刑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背场刑。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工般此, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人牵现。 一個(gè)月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓铐懊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瞎疼。 傳聞我的和親對象是個(gè)殘疾皇子科乎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評論 2 353

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