Java 并發(fā)之 ReentrantReadWriteLock 深入分析

前言

線程并發(fā)系列文章:

Java 線程基礎(chǔ)
Java 線程狀態(tài)
Java “優(yōu)雅”地中斷線程-實(shí)踐篇
Java “優(yōu)雅”地中斷線程-原理篇
真正理解Java Volatile的妙用
Java ThreadLocal你之前了解的可能有誤
Java Unsafe/CAS/LockSupport 應(yīng)用與原理
Java 并發(fā)"鎖"的本質(zhì)(一步步實(shí)現(xiàn)鎖)
Java Synchronized實(shí)現(xiàn)互斥之應(yīng)用與源碼初探
Java 對象頭分析與使用(Synchronized相關(guān))
Java Synchronized 偏向鎖/輕量級鎖/重量級鎖的演變過程
Java Synchronized 重量級鎖原理深入剖析上(互斥篇)
Java Synchronized 重量級鎖原理深入剖析下(同步篇)
Java并發(fā)之 AQS 深入解析(上)
Java并發(fā)之 AQS 深入解析(下)
Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 詳解
Java 并發(fā)之 ReentrantLock 深入分析(與Synchronized區(qū)別)
Java 并發(fā)之 ReentrantReadWriteLock 深入分析
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(原理篇)
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(應(yīng)用篇)
最詳細(xì)的圖文解析Java各種鎖(終極篇)
線程池必懂系列

上篇文章分析了AQS的實(shí)際應(yīng)用之一:ReentrantLock 的實(shí)現(xiàn)。ReentrantLock 和synchronized 都是獨(dú)占鎖楼雹,而AQS還支持共享鎖贮缅,本篇就來分析AQS 共享鎖的實(shí)際應(yīng)用。
通過本篇文章齿坷,你將了解到:

1崎场、共享鎖照雁、獨(dú)享鎖區(qū)別
2、讀鎖的實(shí)現(xiàn)原理
3悬嗓、寫鎖的實(shí)現(xiàn)原理
4包竹、讀寫鎖 tryLock 原理
5周瞎、讀寫鎖的應(yīng)用

1声诸、共享鎖彼乌、獨(dú)享鎖區(qū)別

基本差別

共享鎖慰照、獨(dú)占鎖是在AQS里實(shí)現(xiàn)的,核心是"state"的值:


image.png

如上圖,對于共享鎖來說榛泛,允許多個(gè)線程對state進(jìn)行有效修改噩斟。

讀寫鎖的引入

根據(jù)上面的圖剃允,state 同時(shí)只能表示一種鎖椒楣,要么獨(dú)占鎖牡肉,要么共享鎖统锤。而在實(shí)際的應(yīng)用場景里經(jīng)常會(huì)碰到多個(gè)線程讀煌寇,多個(gè)線程寫的情況逾雄,此時(shí)為了能夠協(xié)同讀、寫線程永品,需要將state改造鼎姐。
先來看AQS state 定義:

#AbstractQueuedSynchronizer.java
private volatile int state;

可以看出是int 類型的(當(dāng)然也有l(wèi)ong 類型的症见,在AbstractQueuedLongSynchronizer.java 里谋作,本文以int 為例)

image.png

state 被分為兩部分遵蚜,低16位表示寫鎖(獨(dú)占鎖)吭净,高16位表示讀鎖(共享鎖),這樣一個(gè)32位的state 就可以同時(shí)表示共享鎖和獨(dú)占鎖了友扰。

2庶柿、讀鎖的實(shí)現(xiàn)原理

ReentrantReadWriteLock 的構(gòu)造

ReentrantReadWriteLock 并沒有像ReentrantLock一樣直接實(shí)現(xiàn)Lock 接口甚负,而是內(nèi)部分別持有ReadLock审残、WriteLock類型的成員變量碰辅,兩者均實(shí)現(xiàn)了Lock 接口介时。

#ReentrantReadWriteLock.java
    public ReentrantReadWriteLock() {
        //默認(rèn)非公平鎖
        this(false);
    }

    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        //構(gòu)造讀鎖
        readerLock = new ReadLock(this);
        //構(gòu)造寫鎖
        writerLock = new WriteLock(this);
    }

ReentrantReadWriteLock 默認(rèn)實(shí)現(xiàn)非公平鎖循衰,讀鎖会钝、寫鎖支持非公平鎖和公平鎖迁酸。
讀寫鎖構(gòu)造之后奸鬓,將鎖暴露出來給外部使用:

#ReentrantReadWriteLock.java
    //獲取寫鎖對象
    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    //獲取讀鎖對象
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

獲取鎖

在ReentrantLock 分析獨(dú)占鎖時(shí)有如下圖:


image.png

與獨(dú)占鎖類似,AQS雖然已經(jīng)實(shí)現(xiàn)了共享鎖的基本邏輯澡罚,但是真正獲取鎖留搔、釋放鎖的操作還是需要子類實(shí)現(xiàn)隔显,共享鎖需要實(shí)現(xiàn)方法:

tryAcquireShared & tryReleaseShared

來看看獲取鎖的過程:

#ReentrantReadWriteLock.ReadLock
    public void lock() {
            //共享鎖
            sync.acquireShared(1);
        }

#AbstractQueuedSynchronizer.java
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            //doAcquireShared 在AQS里實(shí)現(xiàn)
            doAcquireShared(arg);
    }    

重點(diǎn)是tryAcquireShared(xx):

#ReentrantReadWriteLock.java
        protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            //獲取同步狀態(tài)
            int c = getState();
            //此處exclusiveCount作用是取state 低16位,若是不等于0梳毙,說明有線程占有了寫鎖
            //若是有線程占有了寫鎖萌业,而這個(gè)線程不是當(dāng)前線程生年,則直接退出------------>(1)
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            //獲取state 高16位抱婉,若是大于0蒸绩,說明有線程占有了讀鎖
            int r = sharedCount(c);
            //當(dāng)前線程是否應(yīng)該阻塞
            if (!readerShouldBlock() &&//------------>(2)
                r < MAX_COUNT &&//若是不該阻塞传蹈,則嘗試CAS修改state高16位的值
                compareAndSetState(c, c + SHARED_UNIT)) {
                //--------記錄線程/重入次數(shù)----------->(3)
                //修改state 成功步藕,說明成功占有了讀鎖
                if (r == 0) {
                    //記錄第一個(gè)占有讀鎖的線程
                    firstReader = current;
                    //占有次數(shù)為1
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    //第一個(gè)占有讀鎖的線程重入了該鎖
                    firstReaderHoldCount++;
                } else {
                    //是其它線程占有鎖
                    //取出緩存的HoldCounter
                    HoldCounter rh = cachedHoldCounter;
                    //若是緩存為空沾歪,或是緩存存儲(chǔ)的不是當(dāng)前的線程
                    if (rh == null || rh.tid != getThreadId(current))
                        //從threadLocal里獲取
                        //readHolds 為ThreadLocalHoldCounter 類型,繼承自ThreadLocal
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        //說明cachedHoldCounter 已經(jīng)被移出threadLocal仪或,
                        //重新加入即可------------>(4)
                        readHolds.set(rh);
                    //記錄重入次數(shù)
                    rh.count++;
                    //--------記錄線程/重入次數(shù)-----------
                }
                return 1;
            }
            //------------>(5)
            return fullTryAcquireShared(current);
        }

以上是獲取讀鎖的核心代碼蕾域,標(biāo)注了5個(gè)重點(diǎn)旨巷,分別來分析。
(1)
此處表明了一個(gè)信息:

若是當(dāng)前線程已經(jīng)獲取了寫鎖斧吐,那么它可以繼續(xù)嘗試獲得讀鎖煤率。
當(dāng)它把寫鎖釋放后蝶糯,只剩讀鎖了识虚。這個(gè)過程可以理解為鎖的降級舷礼。

(2)
線程能否有機(jī)會(huì)獲取讀鎖,還需要經(jīng)過兩個(gè)判斷:

1团赁、判定readerShouldBlock()。
2怀挠、判定讀鎖個(gè)數(shù)用完了沒,閾值是2^16-1害捕。

而讀鎖公平與否就體現(xiàn)在readerShouldBlock()的實(shí)現(xiàn)上绿淋。

先來看非公平讀鎖:

#ReentrantReadWriteLock.java
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }

#AbstractQueuedSynchronizer.java
       final boolean apparentlyFirstQueuedIsExclusive() {
        //判斷等待隊(duì)列里的第二個(gè)節(jié)點(diǎn)是否在等待寫鎖
        Node h, s;
        return (h = head) != null &&
            (s = h.next)  != null &&
            !s.isShared()         &&
            s.thread != null;
    }

若等待隊(duì)列里的第二個(gè)節(jié)點(diǎn)是在等待寫鎖,那么此時(shí)不能去獲取讀鎖尝盼。
這與ReentrantLock不一樣吞滞,ReentrantLock 非公平鎖的實(shí)現(xiàn)是不管等待隊(duì)列里有沒有節(jié)點(diǎn),都會(huì)去嘗試獲取鎖盾沫。

再來看公平讀鎖

#ReentrantReadWriteLock.java
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }

判斷隊(duì)列里是否有更早于當(dāng)前線程排隊(duì)的節(jié)點(diǎn)裁赠,該方法在上篇分析ReentrantLock 時(shí)有深入分析,此處不再贅述。

(3)
這部分代碼看起來多,實(shí)際上就是為了記錄重入次數(shù)以及為了效率考慮引入了一些緩存掀潮。
考慮到有可能始終只有一個(gè)線程獲取讀鎖薯鼠,因此定義了兩個(gè)變量還記錄重入次數(shù):

#ReentrantReadWriteLock.java
        //記錄第一個(gè)獲取讀鎖的線程
        private transient Thread firstReader = null;
        //第一個(gè)獲取讀鎖的線程獲取讀鎖的個(gè)數(shù)
        private transient int firstReaderHoldCount;

再考慮到有多個(gè)線程獲取鎖,它們也需要記錄獲取鎖的個(gè)數(shù),與線程綁定的數(shù)據(jù)我們想到了ThreadLocal,于是定義了:

private transient ThreadLocalHoldCounter readHolds;

來記錄HoldCounter(存儲(chǔ)獲取鎖的個(gè)數(shù)及綁定的線程id)。
最后為了不用每次都去ThreadLocal里查詢數(shù)據(jù),再定義了變量來緩存HoldCounter:

#ReentrantReadWriteLock.java
private transient HoldCounter cachedHoldCounter;

(4)
cachedHoldCounter.count == 0,是在tryReleaseShared(xx)里操作的,并且判斷當(dāng)線程已經(jīng)徹底釋放了讀鎖后簿透,將HoldCounter 從ThreadLocal里移除啡浊,因此此處需要加回來廷粒。

(5)
走到這一步,說明之前獲取鎖的操作失敗了,原因有三點(diǎn):

1、readerShouldBlock() == true。
2、r >= MAX_COUNT。
3、中途有其它線程修改了state。

fullTryAcquireShared(xx)與tryAcquireShared(xx)很類似,目的就是為了獲取鎖。
針對第三點(diǎn),fullTryAcquireShared(xx)里有個(gè)死循環(huán),不斷獲取state值募判,若是符合1、2點(diǎn)浸船,則退出循環(huán),否則嘗試CAS修改state阔籽,若是失敗,則繼續(xù)循環(huán)獲取state值。

小結(jié)一下:

1、fullTryAcquireShared(xx) 獲取鎖失敗返回-1横漏,接下來的處理邏輯流轉(zhuǎn)到AQS里二蓝,線程可能會(huì)被掛起鸥诽。
2炬藤、fullTryAcquireShared(xx) 獲取鎖成功則返回1帝火。

釋放鎖

釋放鎖的邏輯比較簡單:

#ReentrantReadWriteLock.ReadLock
    public void lock() {
            sync.acquireShared(1);
        }
#AbstractQueuedSynchronizer.java
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            //在AQS里實(shí)現(xiàn)
            doReleaseShared();
            return true;
        }
        return false;
    }

重點(diǎn)是tryReleaseShared(xx):

#ReentrantReadWriteLock.java
        protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            //當(dāng)前線程是之前第一個(gè)獲取讀鎖的線程
            if (firstReader == current) {
                if (firstReaderHoldCount == 1)
                    //徹底釋放完了冕广,置空
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                //先從緩存里取
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    //取不到,則需要從ThreadLocal里取
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    //若是當(dāng)前線程不再占有鎖隘谣,則清除對應(yīng)的ThreadLocal變量
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            for (;;) {
                //修改state
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    //若是state值變?yōu)?弟晚,說明讀鎖、寫鎖都釋放完了
                    return nextc == 0;
            }
        }

此處需要注意的是:
tryReleaseShared(xx)釋放讀鎖時(shí)候怀偷,若是沒有完全釋放讀鎖脓斩、寫鎖照皆,那么將會(huì)返回false。
而在AQS里釋放共享鎖流程如下:

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

也就是說此種情況下败玉,doReleaseShared() 將不會(huì)被調(diào)用财剖,也就不會(huì)喚醒同步隊(duì)列里的節(jié)點(diǎn)匣摘。
這么做的原因是:

若只釋放完讀鎖,還剩寫鎖被占用贯被。而因?yàn)閷戞i是獨(dú)占鎖朱沃,其它線程無法獲取鎖失暴,那么即使喚醒了它們也沒有用。

3喳逛、寫鎖的實(shí)現(xiàn)原理

獲取鎖

寫鎖是獨(dú)占鎖赠法,因此重點(diǎn)關(guān)注tryAcquire(xx):

#ReentrantReadWriteLock.java
        protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            //獲取同步狀態(tài)
            int c = getState();
            //獲取當(dāng)前寫鎖個(gè)數(shù)
            int w = exclusiveCount(c);
            if (c != 0) {
                //1、若是w==0窟坐,而c!= 0懒豹,說明有線程占有了讀鎖片酝,不能再獲取寫鎖了
                //2、若是寫鎖被占用吊圾,但是不是當(dāng)前線程,則不能再獲取寫鎖了
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                //鎖個(gè)數(shù)超限了
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");

                //走到此處,說明重入仆嗦,直接設(shè)置罕袋,同一時(shí)刻只有一個(gè)線程能走到這
                setState(c + acquires);
                return true;
            }
            //若c==0唠摹,此時(shí)讀鎖盗温、寫鎖都沒線程占用
            //判斷線程是否應(yīng)該被阻塞染坯,否則嘗試獲取寫鎖------->(1)
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            //獨(dú)占鎖需要關(guān)聯(lián)線程
            setExclusiveOwnerThread(current);
            return true;
        }

來看看writerShouldBlock()骡技,寫鎖公平/非公平就在此處實(shí)現(xiàn)的唆途。

先來看非公平寫鎖:

#ReentrantReadWriteLock.java
        final boolean writerShouldBlock() {
            //不阻塞
            return false; // writers can always barge
        }

非公平寫鎖不應(yīng)該阻塞。

再來看公平寫鎖:

#ReentrantReadWriteLock.java
        final boolean writerShouldBlock() {
            //判斷隊(duì)列是否有有效節(jié)點(diǎn)等待
            return hasQueuedPredecessors();
        }

和公平讀鎖一樣的判斷條件。

小結(jié)

1、讀鎖/寫鎖 已被其它線程占用医舆,那么新來的線程將無法獲取寫鎖俘侠。
2、寫鎖可重入蔬将。

釋放鎖

釋放鎖重點(diǎn)關(guān)注tryRelease(xx):

##ReentrantReadWriteLock.java
        protected final boolean tryRelease(int releases) {
            //當(dāng)前線程是否持有寫鎖
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //同一時(shí)刻爷速,只有一個(gè)線程會(huì)執(zhí)行到此
            int nextc = getState() - releases;
            //判斷寫鎖是否釋放完畢
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
                //取消關(guān)聯(lián)
                setExclusiveOwnerThread(null);
            //設(shè)置狀態(tài)
            setState(nextc);
            return free;
        }

若tryRelease(xx)返回true,則AQS里會(huì)喚醒等待隊(duì)列的線程霞怀。

4惫东、讀寫鎖 tryLock 原理

讀鎖tryLock

#ReentrantReadWriteLock.java
        public boolean tryLock() {
            return sync.tryReadLock();
        }

        final boolean tryReadLock() {
            Thread current = Thread.currentThread();
            for (;;) {
            //for 循環(huán)為了檢測最新的state
                int c = getState();
                if (exclusiveCount(c) != 0 &&
                    getExclusiveOwnerThread() != current)
                    return false;
                int r = sharedCount(c);
                if (r == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                //記錄次數(shù)
                    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++;
                    }
                    //獲得鎖后退出循環(huán)
                    return true;
                }
            }
        }

可以看出tryReadLock(xx)里: 只要不是別的線程占有寫鎖并且讀鎖個(gè)數(shù)沒超出限制,那么它將一直嘗試獲取讀鎖,直到得到為止廉沮。

寫鎖tryLock

        public boolean tryLock() {
            return sync.tryWriteLock();
        }
        final boolean tryWriteLock() {
            Thread current = Thread.currentThread();
            int c = getState();
            if (c != 0) {
                int w = exclusiveCount(c);
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
            }
            if (!compareAndSetState(c, c + 1))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

寫鎖只嘗試一次CAS颓遏,失敗就返回。
最終滞时,用圖表示讀鎖叁幢、寫鎖實(shí)現(xiàn)的功能:


image.png

讀鎖與寫鎖關(guān)系:


image.png

5、讀寫鎖的應(yīng)用

分析完原理坪稽,來看看簡單應(yīng)用曼玩。

public class TestThread {

    static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

    public static void main(String args[]) {
        //讀
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    String threadName = Thread.currentThread().getName();
                    try {
                        System.out.println("thread " + threadName + " acquire read lock");
                        readLock.lock();
                        System.out.println("thread " + threadName + " read locking");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        readLock.unlock();
                        System.out.println("thread " + threadName + " release read lock remain read count:" + readWriteLock.getReadLockCount());
                    }
                }
            }, "" + i).start();
        }

        //寫
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    String threadName = Thread.currentThread().getName();
                    try {
                        System.out.println("thread " + threadName + " acquire write lock");
                        writeLock.lock();
                        System.out.println("thread " + threadName + " write locking");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        writeLock.unlock();
                        System.out.println("thread " + threadName + " release write lock remain write count:" + readWriteLock.getWriteHoldCount());
                    }
                }
            }, "" + i).start();
        }
    }
}

10個(gè)線程獲取讀鎖,10個(gè)線程獲取寫鎖窒百。
讀寫鎖應(yīng)用場景:

  • ReentrantReadWriteLock 適用于讀多寫少的場景黍判,提高多線程讀的效率、吞吐量篙梢。

同一線程讀鎖顷帖、寫鎖關(guān)系:

public class TestThread {

    static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

    public static void main(String args[]) {
//        new TestThread().testReadWriteLock();------>1、先讀鎖庭猩,后寫鎖
//        new TestThread().testWriteReadLock();------>2窟她、先寫鎖、后讀鎖
    }

    private void testReadWriteLock() {
        System.out.println("before read lock");
        readLock.lock();
        System.out.println("before write lock");
        writeLock.lock();
        System.out.println("after write lock");
    }

    private void testWriteReadLock() {
        System.out.println("before write lock");
        writeLock.lock();
        System.out.println("before read lock");
        readLock.lock();
        System.out.println("after read lock");
    }
}

分別打開1蔼水、2 注釋震糖,發(fā)現(xiàn):

1、先獲取讀鎖趴腋,再獲取寫鎖吊说,則線程在寫鎖處掛起。
2优炬、先獲取寫鎖颁井,再獲取讀鎖,則都能正常獲取鎖蠢护。
這與我們上述的理論分析一致雅宾。

下篇將會(huì)分析Semaphore、CountDownLatch葵硕、 CyclicBarrier原理及其應(yīng)用眉抬。

本文基于jdk1.8。

您若喜歡懈凹,請點(diǎn)贊蜀变、關(guān)注,您的鼓勵(lì)是我前進(jìn)的動(dòng)力

持續(xù)更新中介评,和我一起步步為營系統(tǒng)库北、深入學(xué)習(xí)Android/Java

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末爬舰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子寒瓦,更是在濱河造成了極大的恐慌情屹,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杂腰,死亡現(xiàn)場離奇詭異屁商,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)颈墅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雾袱,“玉大人恤筛,你說我怎么就攤上這事∏巯穑” “怎么了毒坛?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長林说。 經(jīng)常有香客問我煎殷,道長,這世上最難降的妖魔是什么腿箩? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任豪直,我火速辦了婚禮,結(jié)果婚禮上珠移,老公的妹妹穿的比我還像新娘弓乙。我一直安慰自己,他們只是感情好钧惧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布暇韧。 她就那樣靜靜地躺著,像睡著了一般浓瞪。 火紅的嫁衣襯著肌膚如雪懈玻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天乾颁,我揣著相機(jī)與錄音涂乌,去河邊找鬼。 笑死钮孵,一個(gè)胖子當(dāng)著我的面吹牛骂倘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播巴席,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼历涝,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起荧库,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤堰塌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后分衫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體场刑,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年蚪战,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牵现。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,146評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邀桑,死狀恐怖瞎疼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情壁畸,我是刑警寧澤贼急,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站捏萍,受9級特大地震影響太抓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜令杈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一走敌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧这揣,春花似錦悔常、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至片迅,卻和暖如春残邀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背柑蛇。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工芥挣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人耻台。 一個(gè)月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓空免,卻偏偏與公主長得像,于是被迫代替她去往敵國和親盆耽。 傳聞我的和親對象是個(gè)殘疾皇子蹋砚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評論 2 356

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