詳解Java中ThreadPool的使用

線程池就是限制系統中執行線程的數量。我們可以根據系統的環境情況,可以自動或手動設置線程數量,達到運行的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率不高。用線程池控制線程數量,其他線程排隊等候。一個任務執行完畢,再從隊列的中取最前面的任務開始執行。若隊列中沒有等待進程,線程池的這一資源處于等待。當一個新任務需要運行時,如果線程池中有等待的工作線程,就可以開始運行了;否則進入等待隊列。

為什么要用線程池:

  • 1.減少了創建和銷毀線程的次數,每個工作線程都可以被重復利用,可執行多個任務。
  • 2.可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最后死機)。

1.線程池繼承體系

如下圖所示,Java里面線程池的頂級接口是Executor,但是嚴格意義上講Executor并不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService。

2.Executors

要配置一個線程池是比較復雜的,尤其是對于線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在Executors類里面提供了一些靜態工廠,生成一些常用的線程池。

2.1 newSingleThreadExecutor

創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當于單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那么會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。

源碼:

 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

實例

public classMyThread extends Thread {
    @Override
    publicvoid run() {
        System.out.println(Thread.currentThread().getName() + "正在執行。。。");
    }
}
publicclassTestSingleThreadExecutor {
    publicstaticvoid main(String[] args) {
        //創建一個可重用固定線程數的線程池
        ExecutorService pool = Executors. newSingleThreadExecutor();
        //創建實現了Runnable接口對象,Thread對象當然也實現了Runnable接口
        Thread t1 = new MyThread();
        Thread t2 = new MyThread();
        Thread t3 = new MyThread();
        Thread t4 = new MyThread();
        Thread t5 = new MyThread();
        //將線程放入池中進行執行
        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);
        //關閉線程池
        pool.shutdown();
    }
}

運行結果:

pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-1正在執行。。。
2.2 newFixedThreadPool

創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那么線程池會補充一個新線程。

源碼:

   public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

實例

publicclass TestFixedThreadPool {
    publicstaticvoid main(String[] args) {
        //創建一個可重用固定線程數的線程池
        ExecutorService pool = Executors.newFixedThreadPool(2);
        //創建實現了Runnable接口對象,Thread對象當然也實現了Runnable接口
        Thread t1 = new MyThread();
        Thread t2 = new MyThread();
        Thread t3 = new MyThread();
        Thread t4 = new MyThread();
        Thread t5 = new MyThread();
        //將線程放入池中進行執行
        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);
        //關閉線程池
        pool.shutdown();
    }
}

運行結果:

pool-1-thread-1正在執行。。。
pool-1-thread-2正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-2正在執行。。。
pool-1-thread-1正在執行。。。
2.3 newCachedThreadPool

創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,那么就會回收部分空閑(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴于操作系統(或者說JVM)能夠創建的最大線程大小。

源碼:

   public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

實例

publicclass TestCachedThreadPool {
    publicstaticvoid main(String[] args) {
        //創建一個可重用固定線程數的線程池
        ExecutorService pool = Executors.newCachedThreadPool();
        //創建實現了Runnable接口對象,Thread對象當然也實現了Runnable接口
        Thread t1 = new MyThread();
        Thread t2 = new MyThread();
        Thread t3 = new MyThread();
        Thread t4 = new MyThread();
        Thread t5 = new MyThread();
        //將線程放入池中進行執行
        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);
        //關閉線程池
        pool.shutdown();
    }
}

運行結果:


pool-1-thread-2正在執行。。。
pool-1-thread-4正在執行。。。
pool-1-thread-3正在執行。。。
pool-1-thread-1正在執行。。。
pool-1-thread-5正在執行。。。
2.4 newScheduledThreadPool

創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求。

源碼:

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

實例

publicclass TestScheduledThreadPoolExecutor {
    publicstaticvoid main(String[] args) {
        ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);
        exec.scheduleAtFixedRate(new Runnable() {//每隔一段時間就觸發異常
                      @Override
                      publicvoid run() {
                           //throw new RuntimeException();
                           System.out.println("================");
                      }
                  }, 1000, 5000, TimeUnit.MILLISECONDS);
        exec.scheduleAtFixedRate(new Runnable() {//每隔一段時間打印系統時間,證明兩者是互不影響的
                      @Override
                      publicvoid run() {
                           System.out.println(System.nanoTime());
                      }
                  }, 1000, 2000, TimeUnit.MILLISECONDS);
    }
}

運行結果:

================
8384644549516
8386643829034
8388643830710
================
8390643851383
8392643879319
8400643939383

3.ThreadPoolExecutor

從上面Executors類創建幾種線程池的源碼中可以發現,底層調用了ThreadPoolExecutor類的構造方法。下面就具體的看一看這個類的相關源碼。

3.1 構造方法
 /**
     * 用給定的參數創建一個線程池
     *
     * @param corePoolSize 池中所保存的線程數,包括空閑線程。
     * @param maximumPoolSize 池中允許的最大線程數。
     * @param keepAliveTime 當線程數大于核心時,此為終止前多余的空閑線 程等待新任務的最長時間。
     * @param unit 參數的時間單位。
     * @param workQueue 執行前用于保持任務的隊列。此隊列僅保持由 execute方法提交的Runnable任務。
     * @param threadFactory 執行程序創建新線程時使用的工廠。
     * @param handler 由于超出線程范圍和隊列容量而使執行被阻塞時所使用的處理程序。
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
3.2.execute方法

execute方法里面的代碼邏輯對應著用戶提交一個線程時的執行流程,如下流程圖。

源碼:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
3.3 BlockingQueue<Runnable> 參數

線程池的實現,隊列起著至關重要的作用。它也是ThreadPoolExecutor構造方法里面的參數之間。從上面的excute執行邏輯知道當核心線程已滿,如果等待隊列未滿,我們就可以把線程任務放到隊列中去,當線程池中有空閑線程就會執行該任務。

3.3.1 類圖:
ArrayBlockingQueue--聲明時就確定大小的隊列,fifo方式。(方法基本和接口一致,沒有特別要說明的內容)
LinkedBlockingQueue--鏈表實現的queue-remove效率會高一些
PriorityBlockingQueue--優先級隊列
SynchronousQueue--阻塞隊列,必須拿走一個才能放進來一個,也就是最多只有一個~
DelayQuque--就是放進去的內容,延遲時間到了后才可以獲得
LinkedBlockDeque--雙端隊列 :offerFirst/offerLast,pollFirst/pollLast
LinkedTransferQueue--類似LinkedUnBlockedQueue,其實就是transfer方法有人再等待隊列內容就直接給他這個元素,沒人在等就放在隊列里面。也就是效率會更高。
3.3.2 排隊的三種通用策略:
  • 直接提交。工作隊列的默認選項是 SynchronousQueue,它將任務直接提交給線程而不保持它們。在此,如果不存在可用于立即運行任務的線程,則試圖把任務加入隊列將失敗,因此會構造一個新的線程。此策略可以避免在處理可能具有內部依賴性的請求集時出現鎖。直接提交通常要求無界 maximumPoolSizes 以避免拒絕新提交的任務。當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性。
  • 無界隊列。使用無界隊列(例如,不具有預定義容量的 LinkedBlockingQueue)將導致在所有 corePoolSize 線程都忙時新任務在隊列中等待。這樣,創建的線程就不會超過 corePoolSize。(因此,maximumPoolSize的值也就無效了。)當每個任務完全獨立于其他任務,即任務執行互不影響時,適合于使用無界隊列;例如,在 Web頁服務器中。這種排隊可用于處理瞬態突發請求,當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性。
  • 有界隊列。當使用有限的 maximumPoolSizes時,有界隊列(如 ArrayBlockingQueue)有助于防止資源耗盡,但是可能較難調整和控制。隊列大小和最大池大小可能需要相互折衷:使用大型隊列和小型池可以最大限度地降低 CPU 使用率、操作系統資源和上下文切換開銷,但是可能導致人工降低吞吐量。如果任務頻繁阻塞(例如,如果它們是 I/O邊界),則系統可能為超過您許可的更多線程安排時間。使用小型隊列通常要求較大的池大小,CPU使用率較高,但是可能遇到不可接受的調度開銷,這樣也會降低吞吐量。
3.3.3 BlockingQueue的選擇。
  • 例子一:使用直接提交策略,也即SynchronousQueue。
    首先SynchronousQueue是無界的,也就是說他存數任務的能力是沒有限制的,但是由于該Queue本身的特性,在某次添加元素后必須等待其他線程取走后才能繼續添加。在這里不是核心線程便是新創建的線程,但是我們試想一樣下,下面的場景。
new ThreadPoolExecutor(
  2, 3, 30, TimeUnit.SECONDS,
  new SynchronousQueue<Runnable>(),
  new RecorderThreadFactory("CookieRecorderPool"),
  new ThreadPoolExecutor.CallerRunsPolicy());

當核心線程已經有2個正在運行.
此時繼續來了一個任務(A),根據前面介紹的“如果運行的線程等于或多于 corePoolSize,則 Executor始終首選將請求加入隊列,而不添加新的線程。”,所以A被添加到queue中。
又來了一個任務(B),且核心2個線程還沒有忙完,OK,接下來首先嘗試1中描述,但是由于使用的SynchronousQueue,所以一定無法加入進去。
此時便滿足了上面提到的“如果無法將請求加入隊列,則創建新的線程,除非創建此線程超出maximumPoolSize,在這種情況下,任務將被拒絕。”,所以必然會新建一個線程來運行這個任務。
暫時還可以,但是如果這三個任務都還沒完成,連續來了兩個任務,第一個添加入queue中,后一個呢?queue中無法插入,而線程數達到了maximumPoolSize,所以只好執行異常策略了。
所以在使用SynchronousQueue通常要求maximumPoolSize是無界的,這樣就可以避免上述情況發生(如果希望限制就直接使用有界隊列)。對于使用SynchronousQueue的作用jdk中寫的很清楚:此策略可以避免在處理可能具有內部依賴性的請求集時出現鎖。
什么意思?如果你的任務A1,A2有內部關聯,A1需要先運行,那么先提交A1,再提交A2,當使用SynchronousQueue我們可以保證,A1必定先被執行,在A1么有被執行前,A2不可能添加入queue中。

  • 例子二:使用無界隊列策略,即LinkedBlockingQueue
    這個就拿newFixedThreadPool來說,根據前文提到的規則:
    如果運行的線程少于 corePoolSize,則 Executor 始終首選添加新的線程,而不進行排隊。那么當任務繼續增加,會發生什么呢?
    如果運行的線程等于或多于 corePoolSize,則 Executor 始終首選將請求加入隊列,而不添加新的線程。OK,此時任務變加入隊列之中了,那什么時候才會添加新線程呢?
    如果無法將請求加入隊列,則創建新的線程,除非創建此線程超出 maximumPoolSize,在這種情況下,任務將被拒絕。這里就很有意思了,可能會出現無法加入隊列嗎?不像SynchronousQueue那樣有其自身的特點,對于無界隊列來說,總是可以加入的(資源耗盡,當然另當別論)。換句說,永遠也不會觸發產生新的線程!corePoolSize大小的線程數會一直運行,忙完當前的,就從隊列中拿任務開始運行。所以要防止任務瘋長,比如任務運行的實行比較長,而添加任務的速度遠遠超過處理任務的時間,而且還不斷增加,不一會兒就爆了。
  • 例子三:有界隊列,使用ArrayBlockingQueue。
    這個是最為復雜的使用,所以JDK不推薦使用也有些道理。與上面的相比,最大的特點便是可以防止資源耗盡的情況發生。
    舉例來說,請看如下構造方法:
new ThreadPoolExecutor(
    2, 4, 30, TimeUnit.SECONDS,
    new ArrayBlockingQueue<Runnable>(2),
    new RecorderThreadFactory("CookieRecorderPool"),
    new ThreadPoolExecutor.CallerRunsPolicy());

假設,所有的任務都永遠無法執行完。
對于首先來的A,B來說直接運行,接下來,如果來了C,D,他們會被放到queue中,如果接下來再來E,F,則增加線程運行E,F。但是如果再來任務,隊列無法再接受了,線程數也到達最大的限制了,所以就會使用拒絕策略來處理。

3.4 keepAliveTime 參數

由上可知,ThreadPoolExecutor中額定的“工人”數量由corePoolSize決定,當任務數量超過額定工人數量時,將任務緩存在BlockingQueue之中,當發現如果連queue中也放不下時(可能是因為使用有界queue,也可能是使用SynchronousQueue),ThreadPoolExecutor會請求“老板”再派幾個“工人”過來。

接下來發生的事情有兩種情況:

  • 任務不再過來了 - keepAliveTime
  • 任務仍然繼續過來 - RejectedExecutionHandler

對于keepAliveTime參數,jdk中的解釋是:當線程數大于核心時,此為終止前多余的空閑線程等待新任務的最長時間。

有點拗口,其實這個不難理解,在使用了“池”的應用中,大多都有類似的參數需要配置。比如數據庫連接池,DBCP中的maxIdle,minIdle參數。

什么意思?接著上面的解釋,后來向老板派來的工人始終是“借來的”,俗話說“有借就有還”,但這里的問題就是什么時候還了,如果借來的工人剛完成一個任務就還回去,后來發現任務還有,那豈不是又要去借?這一來一往,老板肯定頭也大死了。

合理的策略:既然借了,那就多借一會兒。直到“某一段”時間后,發現再也用不到這些工人時,便可以還回去了。這里的某一段時間便是keepAliveTime的含義,TimeUnit為keepAliveTime值的度量。

總結:

keepAliveTime和maximumPoolSize及BlockingQueue的類型均有關系。如果BlockingQueue是無界的,那么永遠不會觸發maximumPoolSize,自然keepAliveTime也就沒有了意義。

反之,如果核心數較小,有界BlockingQueue數值又較小,同時keepAliveTime又設的很小,如果任務頻繁,那么系統就會頻繁的申請回收線程。

3.5 RejectedExecutionHandler

即拒絕策略,當即使向老板借了工人,但是任務還是繼續過來,還是忙不過來,這時整個隊伍只好拒絕接受了。
RejectedExecutionHandler接口提供了對于拒絕任務的處理的自定方法的機會。在ThreadPoolExecutor中已經默認包含了4中策略,因為源碼非常簡單,這里直接貼出來。

3.5 .1 CallerRunsPolicy

線程調用運行該任務的 execute 本身。此策略提供簡單的反饋控制機制,能夠減緩新任務的提交速度。

 public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

這個策略顯然不想放棄執行任務。但是由于池中已經沒有任何資源了,那么就直接使用調用該execute的線程本身來執行。

3.5 .2 AbortPolicy

處理程序遭到拒絕將拋出運行時 RejectedExecutionException

public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

這種策略直接拋出異常,丟棄任務。

3.5 .3 DiscardPolicy

不能執行的任務將被刪除

  public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

這種策略和AbortPolicy幾乎一樣,也是丟棄任務,只不過他不拋出異常。

3.5 .4 DiscardOldestPolicy

如果執行程序尚未關閉,則位于工作隊列頭部的任務將被刪除,然后重試執行程序(如果再次失敗,則重復此過程)

   public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

該策略就稍微復雜一些,在pool沒有關閉的前提下首先丟掉緩存在隊列中的最早的任務,然后重新嘗試運行該任務。這個策略需要適當小心。
設想:如果其他線程都還在運行,那么新來任務踢掉舊任務,緩存在queue中,再來一個任務又會踢掉queue中最老任務。

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

推薦閱讀更多精彩內容