Java線程池

基本概念

  1. 任務: 就是你自己實現(xiàn)的任務邏輯,一般為Runnable實現(xiàn)類或Callable實現(xiàn)類,不過在線程池中已經(jīng)被封裝成一個FutureTask. 在我們向線程池中提交一個任務的時候,會先判斷目前線程池中的workerCount是否小于核心線程數(shù),如果小于則將這個任務封裝成一個Worker,然后啟動一個新線程;如果不小于則將這個任務添加到工作隊列
  2. 工作隊列: 工作隊列BlockQueue的實現(xiàn)類,它的作用就是用來緩存任務的,因為它本身是線程安全的,所以在向工作隊列的時候不需要格外處理線程安全問題
  3. Worker: 可以認為每個Worker對應一個線程,在我們創(chuàng)建Worker的時候,會傳入一個任務,這個任務就是這個Worker首次要執(zhí)行的邏輯,執(zhí)行完之后它就會去工作隊列拿任務執(zhí)行. 所有的Worker都保存在一個HashSet數(shù)據(jù)結構中,所以在向HashSet添加Worker的時候需要去處理線程安全問題,線程池中是通過ReentrantLock來保證線程安全

工作流程

其實在說這個之前我們可以先考慮一下線程池出現(xiàn)的目的: 因為創(chuàng)建線程需要比較大的開銷,并且線程數(shù)太多的情況下上下文切換比較頻繁,所以我們希望有一種機制來改善它,這就是線程池,改善的核心就是控制線程的數(shù)量,通過暴露接口,可以滿足用戶創(chuàng)建不同場景下的線程池

  1. 來任務了,先創(chuàng)建幾個線程 核心線程數(shù)
  2. 任務太多了,處理不過來,總不能一直創(chuàng)建線程吧,這時候就將任務緩存到 工作隊列
  3. 任務實在是太多,工作隊列都滿了,那就再創(chuàng)建幾個線程吧 最大線程數(shù)
  4. 任務真的真的太多了,還是處理不過來,拒絕吧,提供了幾種 拒絕策略
  5. 其他: 一段時間后,任務太少了,那些一直不工作的線程怎么處理? 空閑時間

使用示例

ExecutorService executorService = new ThreadPoolExecutor(
        1,
        1,
        3,
        TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(2),
        (r) -> new Thread(r),
        (r, executor) -> System.out.println("拒絕"));

for (int i = 0; i < 5; i++) {
    executorService.submit(() -> {
        System.out.println(Thread.currentThread().getName());
        sleep(TimeUnit.MILLISECONDS, 50);
    });
}


----------------------------------------------執(zhí)行結果----------------------------------------------
拒絕
拒絕
Thread-0
Thread-0
Thread-0

ThreadPoolExecutor

線程池的核心實現(xiàn)類,基于ThreadPoolExecutor可以實現(xiàn)滿足不同場景的線程池

  1. acl: 類型為AtomicInteger,該變量包括兩部分內(nèi)容: 低29位用于表示workerCount,即線程池中的線程數(shù),高3位用于表示線程池的狀態(tài),即RUNNING SHUTDOWN STOP TIDYING TERMINATED
  2. 狀態(tài)之間的轉換
RUNNING -> SHUTDOWN
   On invocation of shutdown(), perhaps implicitly in finalize()
(RUNNING or SHUTDOWN) -> STOP
   On invocation of shutdownNow()
SHUTDOWN -> TIDYING
   When both queue and pool are empty
STOP -> TIDYING
   When pool is empty
TIDYING -> TERMINATED
   When the terminated() hook method has completed

構造函數(shù)

public ThreadPoolExecutor(int corePoolSize,
                            int maximumPoolSize,
                            long keepAliveTime,
                            TimeUnit unit,
                            BlockingQueue<Runnable> workQueue,
                            ThreadFactory threadFactory,
                            RejectedExecutionHandler handler) {
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?null : AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
  1. corePoolSize: 核心線程數(shù),提交一個任務時,如果線程池中的線程數(shù)沒有達到核心線程數(shù),則會創(chuàng)建一個新的線程
  2. maximumPoolSize: 最大線程池,工作隊列滿了的情況下,如果線程池中的線程數(shù)沒有達到最大線程數(shù),則會創(chuàng)建一個新線程,否則使用拒絕策略
  3. keepAliveTime: 空閑線程存活時間,工作對立中沒有任務時,線程最大等待時間,其實就是工作隊列的帶時間阻塞
  4. workQueue: 工作隊列,存放任務的
  5. threadFactory: 創(chuàng)建線程工廠類
  6. handler: 線程池滿了情況下,提交任務時對應的拒絕策略,可以自己實現(xiàn),默認提供了幾種

提交任務

// AbstractExecutorService#submit
public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    // 創(chuàng)建一個FutureTask對象
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    // 執(zhí)行 ThreadPoolExecutor#execute
    execute(ftask);
    return ftask;
}
  1. 基于Runnable創(chuàng)建一個FutureTask對象,這樣可以獲取返回值了,因為Runnable沒有返回值,所以這里直接傳null
  2. 調(diào)用ThreadPoolExecutor#execute方法
ThreadPoolExecutor#execute
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
        * Proceed in 3 steps:
        *
        * 1. If fewer than corePoolSize threads are running, try to
        * start a new thread with the given command as its first
        * task.  The call to addWorker atomically checks runState and
        * workerCount, and so prevents false alarms that would add
        * threads when it shouldn't, by returning false.
        *
        * 2. If a task can be successfully queued, then we still need
        * to double-check whether we should have added a thread
        * (because existing ones died since last checking) or that
        * the pool shut down since entry into this method. So we
        * recheck state and if necessary roll back the enqueuing if
        * stopped, or start a new thread if there are none.
        *
        * 3. If we cannot queue task, then we try to add a new
        * thread.  If it fails, we know we are shut down or saturated
        * and so reject the task.
        */

    int c = ctl.get();
    // 如果`workerCount < corePoolSize`,則嘗試創(chuàng)建一個新線程,創(chuàng)建成功就直接返回,失敗繼續(xù)下面的流程
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    
    // 如果線程池正在運行并且將該任務添加到工作隊列成功
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 再次檢查線程池是否正在運行,如果不在運行了就將該任務移除并執(zhí)行拒絕策略
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }

    // 如果`workerCount >= corePoolSize && 工作隊列放不下了`,再次嘗試添加一個新線程,如果添加失敗則執(zhí)行拒絕策略
    else if (!addWorker(command, false))
        reject(command);
}
  1. 如果workerCount < corePoolSize,則嘗試創(chuàng)建一個新線程,創(chuàng)建成功就直接返回,失敗繼續(xù)下面的流程
  2. workerCount >= corePoolSize,再次檢查線程池是否正在運行,如果不在運行了就將該任務移除并執(zhí)行拒絕策略
  3. 如果workerCount >= corePoolSize && 工作隊列放不下了,再次嘗試添加一個新線程,如果添加失敗則執(zhí)行拒絕策略
ThreadPoolExecutor#addWorker

該方法用于嘗試向線程池中添加一個新的線程,如果線程池運行狀態(tài)不正常,則會添加失敗


1. `firstTask`: 任務的具體邏輯,這里是一個`FutureTask`對象
2. `core`: 如果為true,這和`corePoolSize`比較,否則和`maximumPoolSize`比較. 因為執(zhí)行`addWorker`方法只有兩種情況:一種是`workerCount<corePoolSize`;一種是工作隊列已滿,這時需要和`maximumPoolSize`比較
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.  僅在必要時檢查隊列是否為空, 狀態(tài) 第二個括號里的條件估計和SHUTDOWN語義有關,后面再看吧
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
                firstTask == null &&
                ! workQueue.isEmpty()))
            return false;

        // 通過自旋操作更新`workerCount`的值,即:加1
        for (;;) {
            // 獲取目前線程池中的線程數(shù)
            int wc = workerCountOf(c);

            // 因為執(zhí)行`addWorker`方法只有兩種情況:一種是`workerCount<corePoolSize`,這時需要和`corePoolSize`比較; 一種是工作隊列已滿,這時需要和`maximumPoolSize`比較
            if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                return false;

            // 通過CAS操作嘗試對`workerCount`加1,如果成功就跳出最外層循環(huán)
            if (compareAndIncrementWorkerCount(c))
                break retry;

            c = ctl.get();  // Re-read ctl

            // 如果在自旋(內(nèi)循環(huán)更新`workerCount`值)期間,線程池的狀態(tài)發(fā)生變化,重新進入外循環(huán)
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            // 這里的`ReentrantLock`主要作用是保證添加`Worker`到`workers`時是線程安全的,因為`workers`是`HashSet`結構,其本身不是線程安全的
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());

                // 如果線程池正在運行
                if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                    // 如果t.isAlive()這說明線程已經(jīng)被啟動了,這時候直接拋異常,一般不會出現(xiàn) 
                    if (t.isAlive()) // precheck that t is startable
                        throw new fIllegalThreadStateException();

                    // 將該任務添加到`workers`中,`workers`是一個`HashSet`結構,不過這里通過`ReentrantLock`保證它是線程安全的
                    workers.add(w);

                    int s = workers.size();
                    // 更新`largestPoolSize`,該值用于表示線程池中曾經(jīng)達到的最大線程數(shù)
                    if (s > largestPoolSize)
                        largestPoolSize = s;

                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                // 最后啟動線程
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        // 根據(jù)上面的代碼來判斷,如果線程池運行狀態(tài)不正常的時候,會添加`Worker`失敗,然后執(zhí)行`addWorkerFailed`方法
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

注釋已經(jīng)寫的很詳細了,總結一下:

  1. 先通過自旋更新workerCount的值
  2. 添加Workerworkers中時需要通過ReentrantLock保證線程安全,因為workersHashSet結構,其本身不是線程安全的
  3. 線程池運行狀態(tài)不正常時,會添加Worker失敗,此時需要執(zhí)行ThreadPoolExecutor#addWorkerFailed方法
ThreadPoolExecutor#addWorkerFailed

執(zhí)行到這里,說明線程池可能已經(jīng)出現(xiàn)了問題,這時候需要回滾之氣那的操作.即恢復workerCount的值,然后將該Workerworkers中移除,并嘗試停止線程池

private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)
            // 從`HashSet`中移除
            workers.remove(w);
        // 對`workerCount`減1
        decrementWorkerCount();
        // 嘗試停止線程池
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}
  1. HashSet中移除剛剛添加的Worker
  2. workerCount減1
  3. 嘗試停止線程池

執(zhí)行任務

通過上面的代碼可以發(fā)現(xiàn),提交任務的時候,如果創(chuàng)建了一個新的Worker實例,就相當于創(chuàng)建了一個新的線程,并且會啟動該線程. 那線程啟動之后主要做了什么?
Thread#start => Worker#run => ThreadPoolExecutor#runWorker

Worker
  1. 實現(xiàn)了Runnable接口,因此當線程啟動之后,就會執(zhí)行Worker#run方法
  2. 繼承自AbstractQueuedSynchronizer,說明它具有鎖的功能,但它是不可重入鎖
  3. 在構造函數(shù)中,已自己為參數(shù),創(chuàng)建一個線程,并將該線程作為自己的一個屬性thread
Worker(Runnable firstTask) {
    // 設置state=-1,則無法獲取鎖, 在runWorker中會先執(zhí)行unlock方法,然后再執(zhí)行l(wèi)ock方法獲取鎖
    setState(-1); // inhibit interrupts until runWorker
    // 最開始執(zhí)行的那個任務,之后的任務去隊列里面拿
    this.firstTask = firstTask;
    // 以自己為參數(shù)創(chuàng)建一個線程
    this.thread = getThreadFactory().newThread(this);
}

Worker#run方法中,直接調(diào)用了ThreadPoolExecutor#runWorker方法

ThreadPoolExecutor#runWorker
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    // 先取第一個任務執(zhí)行,這是在構造函數(shù)中傳入的
    Runnable task = w.firstTask;
    w.firstTask = null;

    // 因為在Worker構造函數(shù)中默認設置了state為-1,需要先執(zhí)行`Worker#unlock`將state設置為0
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        // 如果首個任務不為null并且工作隊列里面還有任務
        while (task != null || (task = getTask()) != null) {
            w.lock();

            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            // 有點不太懂
            if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted())
                wt.interrupt();

            try {
                // 前置處理,默認為空
                beforeExecute(wt, task);

                Throwable thrown = null;
                try {
                    // 執(zhí)行任務
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    // 后置處理,默認為空
                    afterExecute(task, thrown);
                }
            } finally {
                // 設置task為null, 用于取下一個任務
                task = null;
                // 完成任務數(shù)加1
                w.completedTasks++;
                w.unlock();
            }
        }

        // 工作隊列里面沒任務了,并且在獲取任務時在工作隊列上阻塞的時候大于空閑時間,并且時正常結束的,即沒有發(fā)生什么異常
        completedAbruptly = false;
    } finally {
        // 將該Worker從HashSet中移除,執(zhí)行一些銷毀操作
        processWorkerExit(w, completedAbruptly);
    }
}
  1. 先執(zhí)行firstTask,該任務在創(chuàng)建Worker時傳入
  2. 再從工作隊列中取任務執(zhí)行
  3. 執(zhí)行完成之后,說明runWorker將要退出了,這時候同時需要將該Worker從HashSet中移除

todo 最核心的,中斷處理,即那個判斷條件,也就是Worker實現(xiàn)AQS的目的

空閑線程清理

在創(chuàng)建線程池的時候,有提到一個參數(shù):空閑時間,這個空閑時間是什么意思呢?
Worker執(zhí)行完firstTask之后,就會去工作隊列中拿任務繼續(xù)執(zhí)行,工作隊列是一個阻塞隊列,當工作隊列中沒有任務時,線程就會阻塞,直到有提交了新的任務. 這個空閑時間其實就可以理解成該線程的阻塞時間,這部分邏輯在ThreadPoolExecutor#getTask方法中

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        // allowCoreThreadTimeOut表示線程是否永久存貨, 默認是永久存活, 結合下面的代碼說明在這兩種情況下,空閑時間生效: 1.allowCoreThreadTimeOut==true  2.工作線程數(shù)大于corePoolSize
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}
  1. 空閑時間其實就是線程在阻塞隊列上阻塞的最大時間,即通過阻塞隊列實現(xiàn)
  2. 在這兩種情況下,空閑時間才會生效: allowCoreThreadTimeOut==true 或者 工作線程數(shù)大于corePoolSize

常見線程池

通過Executors可以快速的創(chuàng)建一些不同類型的線程池

ExecutorService#newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
  1. corePoolSize為1摇零,maximumPoolSize為1,意味著線程池中最多只有一個工作線程
  2. 空閑時間為0,表示沒任務立即銷毀該線程
  3. 工作隊列LinkedBlockingQueue,這其實是一個有界的阻塞隊列,但是由于這里沒有在創(chuàng)建LinkedBlockingQueue的時設置容量,所以默認為Integer.MAX_VALUE

優(yōu)缺點

  1. 對阻塞隊列的長度沒有限制,可能會造成OOM

ExecutorService#newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                    60L, TimeUnit.SECONDS,
                                    new SynchronousQueue<Runnable>());
}
  1. corePoolSize為0,maximumPoolSize為Integer.MAX_VALUE地技,意味著線程數(shù)可以為
    Integer.MAX_VALUE
  2. 空閑時間為60s,也就是一分鐘
    3.工作隊列SynchronousQueue,這是一個比較特殊的阻塞隊列,當一個生產(chǎn)者線程向隊列中存數(shù)據(jù)時,生產(chǎn)者線程將被阻塞直到有另一個消費者線程從隊列中取數(shù)據(jù)(即take),反之亦然

優(yōu)缺點

  1. 適合執(zhí)行時間比較短的任務,這種情況下,很多線程可以被復用,避免每次都創(chuàng)建大量線程的開銷
  2. 但在任務執(zhí)行時間比較長的情況,由于該線程池對線程數(shù)沒有限制,可能會創(chuàng)建非常多的線程.

ExecutorService#newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>());
}
  1. corePoolSize為入?yún)ⅲ?code>maximumPoolSize為入?yún)?/li>
  2. 空閑時間為0,表示沒任務立即銷毀該線程
  3. 工作隊列LinkedBlockingQueue,這其實是一個有界的阻塞隊列,但是由于這里沒有在創(chuàng)建LinkedBlockingQueue的時設置容量,所以默認為Integer.MAX_VALUE

這個其實和newSingleThreadExecutor有點像,只不過newSingleThreadExecutor中只有一個線程,而newFixedThreadPool是固定的線程

優(yōu)缺點

  1. 對阻塞隊列的長度沒有限制,可能會造成OOM

ExecutorService#ScheduledThreadPool

public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue(),threadFactory);
}
  1. corePoolSize為入?yún)ⅲ?code>maximumPoolSize為Integer.MAX_VALUE
  2. 空閑時間為0,表示沒任務立即銷毀該線程
  3. 工作隊列DelayedWorkQueue,這是一個有界的阻塞隊列

優(yōu)缺點

  1. 對阻塞隊列的長度沒有限制,可能會造成OOM

總結

還是推薦根據(jù)具體場景,基于ThreadPoolExecutor定制自己的線程池

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市宫患,隨后出現(xiàn)的幾起案子唁盏,更是在濱河造成了極大的恐慌绪钥,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件云芦,死亡現(xiàn)場離奇詭異俯逾,居然都是意外死亡,警方通過查閱死者的電腦和手機舅逸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門桌肴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人琉历,你說我怎么就攤上這事坠七。” “怎么了旗笔?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵彪置,是天一觀的道長。 經(jīng)常有香客問我蝇恶,道長拳魁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任撮弧,我火速辦了婚禮潘懊,結果婚禮上,老公的妹妹穿的比我還像新娘贿衍。我一直安慰自己卦尊,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布舌厨。 她就那樣靜靜地躺著,像睡著了一般忿薇。 火紅的嫁衣襯著肌膚如雪裙椭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天署浩,我揣著相機與錄音揉燃,去河邊找鬼。 笑死筋栋,一個胖子當著我的面吹牛炊汤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼抢腐,長吁一口氣:“原來是場噩夢啊……” “哼姑曙!你這毒婦竟也來了?” 一聲冷哼從身側響起迈倍,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤伤靠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后啼染,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宴合,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年迹鹅,在試婚紗的時候發(fā)現(xiàn)自己被綠了卦洽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡斜棚,死狀恐怖阀蒂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情打肝,我是刑警寧澤脂新,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站粗梭,受9級特大地震影響争便,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜断医,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一滞乙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鉴嗤,春花似錦斩启、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至硬耍,卻和暖如春垄琐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背经柴。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工狸窘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人坯认。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓翻擒,卻偏偏與公主長得像氓涣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子陋气,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344