Java - Queue

簡(jiǎn)介

Queue,翻譯成隊(duì)列,是一種先進(jìn)先出(FIFO, First In First Out)的數(shù)據(jù)結(jié)構(gòu)。最先放進(jìn)去的,取的時(shí)候也就最先取出來(lái)。最形象的比喻就是我們常見的排隊(duì)就是一個(gè)隊(duì)列。排隊(duì)時(shí),新來(lái)的人進(jìn)入隊(duì)尾,先到的人首先接收服務(wù)。

Queue大多數(shù)是單向隊(duì)列,即只能從一端取數(shù)據(jù),另一端放入數(shù)據(jù)。就像把羽毛球放入球筒,從一端放入,從另一端取出。

image.png

Queue體系

public interface Queue<E> extends Collection<E> {
    boolean add(E e);
    boolean offer(E e);
    E remove();
    E poll();
    E element();
    E peek();
}
  • boolean add(E e):向queue中添加element。成功返回true。不會(huì)返回false。當(dāng)添加失敗(比如queue有大小限制),則拋出異常IllegalStateException
  • boolean offer(E e):跟add類似。區(qū)別在于add插入失敗會(huì)拋出異常,offer會(huì)返回false。
  • E remove(): 返回第一個(gè)element,并從queue中刪除。queue為空則拋出異常NoSuchElementException
  • E pool(): 返回第一個(gè)element,并從queue中刪除。queue為空則返回null
  • E element(): 返回第一個(gè)element,但是不從queue中刪除。queue為空則拋出異常NoSuchElementException
  • E peak():返回第一個(gè)element,但是不從queue中刪除。queue為空則拋出異常

上面的6個(gè)方法分別表示了3種操作,每一種操作都有兩種類型,拋出異常的類型有特定返回值的類型。下面用表格來(lái)描述上述方法的異同:

方法 作用 返回值 成功 失敗
add(E e) 插入元素 boolean true IllegalStateException
offer(E e) 同add() boolean true false
remove(E e) 返回第一個(gè)元素,并刪除 E E NoSuchElementException
pool(E e) 同remove boolean E null
element() 返回第一個(gè)元素,不刪除 E E NoSuchElementException
peak() 同element E E null

Queue接口常見的擴(kuò)展和實(shí)現(xiàn)有:

image.png

  • AbstractQueue: 實(shí)現(xiàn)部分方法的抽象類。做為大多數(shù)具體實(shí)現(xiàn)類的基類。
  • BlockingQueue: 阻塞隊(duì)列。在Queue的基礎(chǔ)上增加了阻塞接口。
  • Deque: 雙向隊(duì)列,double ended queue的縮寫。隊(duì)列兩端都可以操作隊(duì)列。

AbstractQueue

AbstractQueue類似于很多JDK源碼中的Abstract*類,實(shí)現(xiàn)了部分通用方法,做為具體實(shí)現(xiàn)類的基類。這里主要講一下PriorityQueueConcurrentLinkedQueue

PriorityQueue

基于堆實(shí)現(xiàn)的優(yōu)先隊(duì)列。獲取數(shù)據(jù)時(shí)是有序的。默認(rèn)是升序。
主要特點(diǎn):

  • 無(wú)界隊(duì)列。隊(duì)列是用數(shù)組實(shí)現(xiàn),需要指定大小,數(shù)組大小可以動(dòng)態(tài)增加,容量無(wú)限。
  • 要實(shí)現(xiàn)插入的元素有序,有兩種方法:
    • 插入元素實(shí)現(xiàn)了Comparable接口
    • 在構(gòu)造函數(shù)中傳入Comparator實(shí)現(xiàn),如下
public class PriorityQueue<E> extends AbstractQueue<E>{
    public PriorityQueue(int initialCapacity, Comparator<? super E> comparator);
}
  • 非線程安全。如需線程安全的實(shí)現(xiàn),請(qǐng)使用PriorityBlockingQueue
  • 插入的元素不能為null
  • 使用for-eachiterator來(lái)遍歷優(yōu)先隊(duì)列,得到的結(jié)果并不保證有序。只有通過不斷調(diào)用poll()/remove()方法得到的結(jié)果才是有序的。如果需要按順序遍歷,請(qǐng)考慮使用 Arrays.sort(pq.toArray())。如下:
PriorityQueue<Integer> test = new PriorityQueue<>();
test.add(10);
test.add(4);
test.add(7);
test.add(2);
test.add(9);

//可保證有序輸出,輸出 2 4 7 9 10
while (!test.isEmpty()) {
    System.out.println(test.poll());
}

//不保證有序輸出,輸出 2 4 7 10 9
for(Integer e : test){
    System.out.println(e);
}

//不保證有序輸出,輸出 2 4 7 10 9
Iterator it = test.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

ConcurrentLinkedQueue

ConcurrentLinkedQueue是一個(gè)無(wú)鎖線程安全隊(duì)列。
常見的線程安全實(shí)現(xiàn)都是通過加鎖(在“線程安全的實(shí)現(xiàn)”這一節(jié)就能看到),而加鎖的成本是很高的。如果能找到一種方法,既不用加鎖,又能保證線程安全,很大可能能極大提升系統(tǒng)性能。ConcurrentLinkedQueue就是使用了這種無(wú)鎖方法的一種隊(duì)列。
其實(shí)現(xiàn)思想借鑒了很通用,也很重要的CAS操作。CAS簡(jiǎn)介
詳情請(qǐng)見無(wú)鎖隊(duì)列 - ConcurrentLinkedQueue

BlockingQueue

BlockingQueue名為阻塞隊(duì)列
何為阻塞?以從隊(duì)列中取數(shù)據(jù)為例,當(dāng)隊(duì)列為空時(shí),Queue提供方法的表現(xiàn)為:

  • remove(): 拋出異常NoSuchElementException
  • poll(): 返回null
    這種情況下,如果想在隊(duì)列不空時(shí)獲取數(shù)據(jù),只能通過循環(huán)不斷調(diào)用remove()poll()。那么,如果能在隊(duì)列為空的時(shí)候,方法不返回,而是等待數(shù)據(jù),直到隊(duì)列中有了數(shù)據(jù),才繼續(xù)進(jìn)行,就方便很多了。這就是阻塞方法。

BlockingQueueQueue的基礎(chǔ)上主要擴(kuò)展了以下幾個(gè)阻塞方法:

public interface BlockingQueue<E> extends Queue<E> {
    void put(E e) throws InterruptedException;
    E take() throws InterruptedException;
    boolean offer(E e, long timeout, TimeUnit unit)
       throws InterruptedException;
    E poll(long timeout, TimeUnit unit)
        throws InterruptedException;
}
  • void put(E e): 作用同add(E e),當(dāng)隊(duì)列滿時(shí)阻塞,直到隊(duì)列不滿。
  • boolean take(): 作用同remove(),當(dāng)隊(duì)列為空時(shí)阻塞,直到隊(duì)列不空。
  • boolean offer(E e, long timeout, TimeUnit unit): 作用同offer(E e),只是增加了超時(shí),允許等一段時(shí)間。如果過了超時(shí)時(shí)間還不能插入成功,則返回false。
  • E poll(long timeout, TimeUnit unit): 作用同poll(),也是增加了超時(shí)。如果過了超時(shí)時(shí)間還不能獲取數(shù)據(jù),則返回null。
異常行為 插入數(shù)據(jù) 取數(shù)據(jù)
拋出異常 add(E e) remove()
返回null offer(E e) poll()
阻塞 put(E e)/offer(E e, long timeout) take()/poll(long timeout)

BlockingQueue的具體實(shí)現(xiàn)

BlockingQueue只是個(gè)接口,其常用的具體實(shí)現(xiàn)有哪些呢?主要有以下這些:

image.png

  • ArrayBlockingQueue: 基于數(shù)組的隊(duì)列,有界隊(duì)列,必須指定大小。
  • LinkedBlockingQueue: 基于鏈表的隊(duì)列,無(wú)界隊(duì)列,大小可指定,可不指定。
  • PriorityBlockingQueue: 優(yōu)先隊(duì)列,或者說(shuō)有序隊(duì)列。隊(duì)列中的元素按照指定規(guī)則排好序。
  • SynchronousQueue: 沒有任何容量(capacity)的隊(duì)列,是一個(gè)比較特殊的隊(duì)列。
  • DelayQueue: 延遲隊(duì)列。放入的元素必須過了超時(shí)時(shí)間才能取出。放入的元素必須實(shí)現(xiàn)Delayed接口

ArrayBlockingQueue

主要特點(diǎn):

  • 有界隊(duì)列
  • 基于數(shù)組存儲(chǔ),數(shù)組長(zhǎng)度固定,需要在構(gòu)造函數(shù)中指定
public class ArrayBlockingQueue<E> extends AbstractQueue<E> 
    implements BlockingQueue<E>, java.io.Serializable {
    final Object[] items;  //基于數(shù)組
    final ReentrantLock lock;  //線程同步鎖
    private final Condition notEmpty;  //條件變量,用于取數(shù)據(jù)同時(shí)隊(duì)列為空時(shí)阻塞線程
    private final Condition notFull;  //條件變量,用戶插入數(shù)據(jù)同時(shí)隊(duì)列滿時(shí)阻塞線程
}

LinkedBlockingQueue

主要特點(diǎn):

  • 可以是無(wú)界隊(duì)列,也可以是有界隊(duì)列。區(qū)別在于是否設(shè)定隊(duì)列大小。
  • 基于鏈表存儲(chǔ)。
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable {
    private transient Node<E> head;  //隊(duì)列頭
    private final ReentrantLock takeLock = new ReentrantLock();  //取數(shù)據(jù)線程同步鎖
    private final Condition notEmpty = takeLock.newCondition(); //條件變量,用于取數(shù)據(jù)同時(shí)隊(duì)列為空時(shí)阻塞線程
    private final ReentrantLock putLock = new ReentrantLock(); //插入數(shù)據(jù)線程同步鎖
    private final Condition notFull = putLock.newCondition(); //條件變量,用戶插入數(shù)據(jù)同時(shí)隊(duì)列滿時(shí)阻塞線程
}

注:put(E e)方法只有在隊(duì)列滿時(shí)才組設(shè),因此,如果是無(wú)界隊(duì)列,put(E e)永遠(yuǎn)不會(huì)阻塞。

PriorityBlockingQueue

優(yōu)先隊(duì)列PriorityQueue的線程安全版本,同時(shí)提供阻塞方法。
主要特點(diǎn)同PriorityQueue

public class PriorityBlockingQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable {
    private transient Object[] queue;
    
}

SynchronousQueue

比較特殊的隊(duì)列,沒有任何存儲(chǔ)空間。
舉個(gè)簡(jiǎn)單的例子:
A是個(gè)快遞員,要送快遞給用戶B,如果使用ArrayBlockingQueue或者LinkedBlockingQueue,會(huì)是這樣:

  1. A把快遞放到快遞柜的箱子里(假設(shè)快遞柜有20個(gè)箱子)
  2. 如果有空箱子,可以直接把快遞放到空箱子中。
  3. 如果所有的箱子都滿了,那么A等著,直到B取了任意快遞,空出了箱子,A再把快遞放到空箱子中。
  4. 如果所有的箱子都空了,取快遞的人B會(huì)一直等待,直到有快遞投遞到箱子中。

那么如果使用SynchronousQueue,情況就不同了

  1. 首先沒有能臨時(shí)存放快遞的柜子和箱子
  2. A要送快遞,會(huì)一直拿著快遞等,直到B來(lái)取。
  3. B要取快遞,也會(huì)一直等,直到A來(lái)送。
    SynchronousQueue就是類似這種“手到手”的交付方式,不經(jīng)過任何媒介緩存。

主要特點(diǎn):

  • 沒有任何數(shù)據(jù)結(jié)構(gòu)可保存數(shù)據(jù),不能調(diào)用peek()方法來(lái)看隊(duì)列中是否有數(shù)據(jù)元素
  • 不能遍歷隊(duì)列,應(yīng)為根本沒有存儲(chǔ)任何數(shù)據(jù)
  • 調(diào)用put()方法會(huì)等待,直到有其他線程調(diào)用了take()方法

DelayQueue

DelayQueue的繼承結(jié)構(gòu)如下:

image.png

public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> {
    private final PriorityQueue<E> q = new PriorityQueue<E>();  //存儲(chǔ)數(shù)據(jù)
}

由類的聲明可知,插入到DelayQueue中的元素必須實(shí)現(xiàn)Delayed接口。Delayed接口比較簡(jiǎn)單,只有一個(gè)getDelay(TimeUnit unit)方法。

public interface Delayed extends Comparable<Delayed> {
    long getDelay(TimeUnit unit);  //剩余時(shí)間
}

同時(shí),DelayQueue存儲(chǔ)數(shù)據(jù)既不像ArrayBlockingQueue使用數(shù)組,也不像LinkedBlockingQueue使用鏈表,而是使用現(xiàn)成的PriorityQueue。因此DelayQueue取數(shù)據(jù)的規(guī)則跟PriorityQueue類似。

何謂延遲?
延遲主要體現(xiàn)在取數(shù)據(jù)的時(shí)候。通過查看poll()的源碼可知:

public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        E first = q.peek();
        if (first == null || first.getDelay(TimeUnit.NANOSECONDS) > 0)
            return null;
        else
            return q.poll();
    } finally {
        lock.unlock();
    }
}

在取數(shù)的時(shí)候,getDelay()必須小于等于0才能把數(shù)取出來(lái)。通過實(shí)現(xiàn)getDelay()方法,就能實(shí)現(xiàn)過多長(zhǎng)時(shí)間以后才能取出數(shù)據(jù)這種延遲效果。

主要特點(diǎn):

  • 無(wú)界隊(duì)列,因此put(E e)不會(huì)阻塞
  • 調(diào)用取元素的方法:remove()poll()take()時(shí),只有當(dāng)元素超時(shí)以后才能取到。

BlockingQueue的應(yīng)用

實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者
生產(chǎn)者-消費(fèi)者模式在工程之中應(yīng)用廣泛。BlockingQueue可極大簡(jiǎn)化實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者的難度。
偽代碼:

//生產(chǎn)者
public class Producer{
    private BlockingQueue queue;
    Producer(BlockingQueue queue){this.queue = queue}
    public void produce(E e){
        //生產(chǎn)者調(diào)用put()方法,隊(duì)列滿時(shí)阻塞等待。
        queue.put(e);
    }
}

//消費(fèi)者
public class Consumer{
    private BlockingQueue queue;
    Consumer(BlockingQueue queue){this.queue = queue}
    public E consume(){
        //消費(fèi)者調(diào)用take()方法,隊(duì)列空時(shí)阻塞等待
        queue.take(e);
    }
}

做為線程池的等待隊(duì)列

  • 線程池ThreadPoolExecutor的構(gòu)造函數(shù)接收BlockingQueue做為等待隊(duì)列
  • Executors.newSingleThreadPool()/Executors.newFixedThreadPool()默認(rèn)使用LinkedBlockingQueue做為等待隊(duì)列
  • Executors.newCachedThreadPool()默認(rèn)使用SynchronousQueue做為等待隊(duì)列。可以做到一有請(qǐng)求,就創(chuàng)建新線程。

求Top K大/小的元素
比如有1億個(gè)隨機(jī)數(shù)字,找出最大的10個(gè)數(shù)。這種類似的求Top K的問題很常見。
由于PriorityQueue/PriorityBlockingQueue底層結(jié)構(gòu)是堆(大頂堆/小頂堆),而解決Top K問題的最好辦法就是使用堆,因此PriorityQueue/PriorityBlockingQueue是解決該問題的不二選擇。
代碼摘要:

public class FixSizedPriorityQueue<E extends Comparable> {
    private PriorityQueue<E> queue;
    private int maxSize; // 堆的最大容量

    public FixSizedPriorityQueue(int maxSize) {
        if (maxSize <= 0)
            throw new IllegalArgumentException();
        this.maxSize = maxSize;
        this.queue = new PriorityQueue(maxSize, new Comparator<E>() {
            public int compare(E o1, E o2) {
                // 生成最大堆使用o2-o1,生成最小堆使用o1-o2, 并修改 e.compareTo(peek) 比較規(guī)則
                return (o2.compareTo(o1));
            }
        });
    }

    public void add(E e) {
        if (queue.size() < maxSize) { // 未達(dá)到最大容量,直接添加
            queue.add(e);
        } else { // 隊(duì)列已滿
            E peek = queue.peek();
            if (e.compareTo(peek) < 0) { // 將新元素與當(dāng)前堆頂元素比較,保留較小的元素
                queue.poll();
                queue.add(e);
            }
        }
    }
}

延時(shí)需求
如:考試時(shí)間為120分鐘,30分鐘后才可交卷。這種情況下,就需要使用到DelayQueue了。
案例參考

Deque

Deque發(fā)音為'deck',為雙向隊(duì)列。
Queue默認(rèn)是單向隊(duì)列,數(shù)據(jù)只能從一頭放入,從另一頭取出,這跟羽毛球的放取原理一樣。雙向隊(duì)列就像放取乒乓球,可以從兩端放入,也可以從兩端取出。

image.png

Deque接口的部分組成

public interface Deque<E> extends Queue<E> {
    //新增方法
    void addFirst(E e);
    void addLast(E e);
    boolean offerFirst(E e);
    boolean offerLast(E e);
    E removeFirst();
    E removeLast();
    E pollFirst();
    E pollLast();
    E getFirst();
    E getLast();
    E peekFirst();
    E peekLast();
    void push(E e);
    E pop();

    //Queue接口方法
    boolean add(E e);
    boolean offer(E e);
    E remove();
    E poll();
    E element();
    E peek();
}

從擴(kuò)展的函數(shù)名字就能看出來(lái),Deque分別針對(duì)插入和取出接口提供了對(duì)隊(duì)列頭和尾的操作。
同時(shí),從Queue繼承過來(lái)方法的與擴(kuò)展方法有如下對(duì)應(yīng)關(guān)系:

add(E e) == addLast(E e)
offer(E e) == offerLast(E e)
remove() == removeFirst()
poll() == pollFirst()
element() == getFirst()
peek() == peekFirst()

同時(shí),Deque還擴(kuò)展出了push(E e)pop()方法。其中:

push(E e)等同于addFirst(E e)
pop()等同于removeFirst()

因此,當(dāng)Deque只通過push(E e)pop()操作隊(duì)列頭時(shí),Deque就演化成了一個(gè)棧Stack

[圖片上傳失敗...(image-f152e6-1551348671199)]

擴(kuò)展接口或?qū)崿F(xiàn)類

  • ArrayDeque: 基于數(shù)組存儲(chǔ)的雙向隊(duì)列
  • LinkedList: 基于鏈表存儲(chǔ)的雙向隊(duì)列
  • ConcurrentLinkedDeque: 基于鏈表的線程安全版本
  • BlockingDeque: 擴(kuò)展了Deque接口的阻塞接口,同時(shí)繼承了BlockingQueue接口。
  • LinkedBlockingDeque: 實(shí)現(xiàn)了BlockingDeque的雙向隊(duì)列。

線程安全的實(shí)現(xiàn)

引申
Java線程安全...

BlockingQueue中的方法都是線程安全的,都使用了ReentrantLock做為鎖。如:
ArrayBlockingQueue
ArrayBlockingQueue中有公共變量count來(lái)計(jì)數(shù)元素個(gè)數(shù),因此需要一個(gè)全局的鎖來(lái)保護(hù)。

public ArrayBlockingQueue(int capacity, boolean fair) {
    lock = new ReentrantLock(fair);
}
public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //do something
    } finally {
        lock.unlock();
    }
}

public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //do something
    } finally {
        lock.unlock();
    }
}

LinkedBlockingQueue
LinkedBlockingQueue中計(jì)數(shù)元素個(gè)數(shù)使用的是AtomicInteger類型,本身是線程安全的。因此沒有使用全局鎖,而是針對(duì)插入和獲取分別創(chuàng)建了兩個(gè)鎖。

private final ReentrantLock takeLock = new ReentrantLock();
private final ReentrantLock putLock = new ReentrantLock();

public boolean offer(E e) {
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        //do something
    } finally {
        putLock.unlock();
    }
}

public E poll() {
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        //do something
    } finally {
        takeLock.unlock();
    }
}

引申
上面實(shí)現(xiàn)線程安全的方式都是通過鎖。而我們知道,鎖是一個(gè)比較重的操作,在高并發(fā)系統(tǒng)中,能少用就少用。因此,在此介紹一個(gè)不用鎖就能實(shí)現(xiàn)線程安全的隊(duì)列:
無(wú)鎖隊(duì)列 - ConcurrentLinkedQueue...
無(wú)鎖并且保證線程安全的思想是使用CAS...

阻塞算法的實(shí)現(xiàn)

引申
Java線程通信...

BlockingQueue的功能是個(gè)標(biāo)準(zhǔn)的生產(chǎn)者-消費(fèi)者模式。線程間通信使用的是條件變量Condition。偽代碼如下:

ReentrantLock lock = new ReentrantLock(fair);
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();

public boolean put(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //如果隊(duì)列已經(jīng)滿了,那么等待隊(duì)列notFull
        while(count == container.size){
            notFull.await()
        }
        notEmpty.signal();
        //do other things
    } finally {
        lock.unlock();
    }
}

public E take() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //如果隊(duì)列為空,那么等待隊(duì)列notEmpty
        while (count == 0){
            notEmpty.await();
        }
        notFull.signal();
    } finally {
        lock.unlock();
    }
}

非阻塞型
ConcurrentLinkedQueue: 無(wú)鎖線程安全隊(duì)列

參考

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

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