并發(fā)編程之線程池的使用及擴(kuò)展和優(yōu)化

前言

多線程的軟件設(shè)計(jì)方法確實(shí)可以最大限度的發(fā)揮現(xiàn)代多核處理器的計(jì)算能力幢哨,提高生產(chǎn)系統(tǒng)的吞吐量和性能雄人。但是,如果一個(gè)系統(tǒng)同時(shí)創(chuàng)建大量線程矩距,線程間頻繁的切換上下文導(dǎo)致的系統(tǒng)開銷將會(huì)拖慢整個(gè)系統(tǒng)拗盒。嚴(yán)重的甚至導(dǎo)致內(nèi)存耗盡導(dǎo)致OOM異常。因此锥债,在實(shí)際的生產(chǎn)環(huán)境中陡蝇,線程的數(shù)量必須得到控制痊臭,盲目的創(chuàng)建大量新車對(duì)系統(tǒng)是有傷害的。

那么登夫,怎么才能最大限度的利用CPU的性能广匙,又能保持系統(tǒng)的穩(wěn)定性呢?其中有一個(gè)方法就是使用線程池恼策。

簡(jiǎn)而言之鸦致,在使用線程池后,創(chuàng)建線程便處理從線程池獲得空閑線程涣楷,關(guān)閉線程變成了向池子歸還線程分唾。也就是說,提高了線程的復(fù)用狮斗。

而 JDK 在 1.5 之后為我提供了現(xiàn)成的線程池工具鳍寂,我們今天就來學(xué)習(xí)看看如何使用他們。

  1. Executors 線程池工廠能創(chuàng)建哪些線程池
  2. 如何手動(dòng)創(chuàng)建線程池
  3. 如何擴(kuò)展線程池
  4. 如何優(yōu)化線程池的異常信息
  5. 如何設(shè)計(jì)線程池中的線程數(shù)量

1. Executors 線程池工廠能創(chuàng)建哪些線程池

先來一個(gè)最簡(jiǎn)單的線程池使用例子:

  static class MyTask implements Runnable {

    @Override
    public void run() {
      System.out
          .println(System.currentTimeMillis() + ": Thread ID :" + Thread.currentThread().getId());
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

  public static void main(String[] args) {
    MyTask myTask = new MyTask();
    ExecutorService service1 = Executors.newFixedThreadPool(5);
    for (int i = 0; i < 10; i++) {
      service1.submit(myTask);
    }
    service1.shutdown();
  }

運(yùn)行結(jié)果:

運(yùn)行結(jié)果

我們創(chuàng)建了一個(gè)線程池實(shí)例情龄,并設(shè)置默認(rèn)線程數(shù)量為5,并向線程池提交了10任務(wù)捍壤,分別打印當(dāng)前毫秒時(shí)間和線程ID骤视,從結(jié)果中,我們可以看到結(jié)果中有5個(gè)相同 id 的線程打印了毫秒時(shí)間鹃觉。

這是最簡(jiǎn)單的例子专酗。

接下來我們講講其他的線程創(chuàng)建方式。

1. 固定線程池
ExecutorService service1 = Executors.newFixedThreadPool(5);
該方法返回一個(gè)固定線程數(shù)量的線程池盗扇。該線程池中的線程數(shù)量始終不變祷肯。當(dāng)有一個(gè)新的任務(wù)提交時(shí),線程池中若有空閑線程疗隶,則立即執(zhí)行佑笋,若沒有,則新的任務(wù)會(huì)被暫存在一個(gè)任務(wù)隊(duì)列(默認(rèn)無界隊(duì)列 int 最大數(shù))中斑鼻,待有線程空閑時(shí)蒋纬,便處理在任務(wù)隊(duì)列中的任務(wù)。

2. 單例線程池
ExecutorService service3 = Executors.newSingleThreadExecutor();
該方法返回一個(gè)只有一個(gè)線程的線程池坚弱。若多余一個(gè)任務(wù)被提交到該線程池蜀备,任務(wù)會(huì)被保存在一個(gè)任務(wù)隊(duì)列(默認(rèn)無界隊(duì)列 int 最大數(shù))中,待線程空閑荒叶,按先入先出的順序執(zhí)行隊(duì)列中的任務(wù)碾阁。

3. 緩存線程池
ExecutorService service2 = Executors.newCachedThreadPool();
該方法返回一個(gè)可根據(jù)實(shí)際情況調(diào)整線程數(shù)量的線程池,線程池的線程數(shù)量不確定些楣,但若有空閑線程可以復(fù)用脂凶,則會(huì)優(yōu)先使用可復(fù)用的線程宪睹,所有線程均在工作,如果有新的任務(wù)提交艰猬,則會(huì)創(chuàng)建新的線程處理任務(wù)横堡。所有線程在當(dāng)前任務(wù)執(zhí)行完畢后,將返回線程池進(jìn)行復(fù)用冠桃。

4. 任務(wù)調(diào)用線程池
ExecutorService service4 = Executors.newScheduledThreadPool(2);
該方法也返回一個(gè) ScheduledThreadPoolExecutor 對(duì)象命贴,該線程池可以指定線程數(shù)量。

前3個(gè)線程的用法沒什么差異食听,關(guān)鍵是第四個(gè)胸蛛,雖然線程任務(wù)調(diào)度框架很多,但是我們?nèi)匀豢梢詫W(xué)習(xí)該線程池樱报。如何使用呢葬项?下面來個(gè)例子:

class A {

  public static void main(String[] args) {
    ScheduledThreadPoolExecutor service4 = (ScheduledThreadPoolExecutor) Executors
        .newScheduledThreadPool(2);

    // 如果前面的任務(wù)沒有完成,則調(diào)度也不會(huì)啟動(dòng)
    service4.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
        try {
          // 如果任務(wù)執(zhí)行時(shí)間大于間隔時(shí)間迹蛤,那么就以執(zhí)行時(shí)間為準(zhǔn)(防止任務(wù)出現(xiàn)堆疊)民珍。
          Thread.sleep(10000);
          System.out.println(System.currentTimeMillis() / 1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }// initialDelay(初始延遲) 表示第一次延時(shí)時(shí)間 ; period 表示間隔時(shí)間
    }, 0, 2, TimeUnit.SECONDS);


    service4.scheduleWithFixedDelay(new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(5000);
          System.out.println(System.currentTimeMillis() / 1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }// initialDelay(初始延遲) 表示延時(shí)時(shí)間;delay + 任務(wù)執(zhí)行時(shí)間 = 等于間隔時(shí)間 period
    }, 0, 2, TimeUnit.SECONDS);

    // 在給定時(shí)間盗飒,對(duì)任務(wù)進(jìn)行一次調(diào)度
    service4.schedule(new Runnable() {
      @Override
      public void run() {
        System.out.println("5 秒之后執(zhí)行 schedule");
      }
    }, 5, TimeUnit.SECONDS);
  }
  }

}

上面的代碼創(chuàng)建了一個(gè) ScheduledThreadPoolExecutor 任務(wù)調(diào)度線程池嚷量,分別調(diào)用了3個(gè)方法,需要著重解釋 scheduleAtFixedRate 和 scheduleWithFixedDelay 方法逆趣,這兩個(gè)方法的作用很相似蝶溶,唯一的區(qū)別就是他們執(zhí)行人物的間隔時(shí)間的計(jì)算方式,前者時(shí)間間隔算法是根據(jù)指定的 period 時(shí)間和任務(wù)執(zhí)行時(shí)間中取時(shí)間長(zhǎng)的宣渗,后者取的是指定的 delay 時(shí)間 + 任務(wù)執(zhí)行時(shí)間抖所。如果同學(xué)們有興趣,可以將上面的代碼跑跑看痕囱。一樣便能看出端倪田轧。

好了,JDK 給我們封裝了創(chuàng)建線程池的 4 個(gè)方法鞍恢,但是涯鲁,請(qǐng)注意,由于這些方法高度封裝有序,因此抹腿,如果使用不當(dāng),出了問題將無從排查旭寿,因此警绩,我建議,程序員應(yīng)到自己手動(dòng)創(chuàng)建線程池盅称,而手動(dòng)創(chuàng)建的前提就是高度了解線程池的參數(shù)設(shè)置肩祥。那么我們就來看看如何手動(dòng)創(chuàng)建線程池后室。

2. 如何手動(dòng)創(chuàng)建線程池

下面是一個(gè)手動(dòng)創(chuàng)建線程池的范本:

  /**
   * 默認(rèn)5條線程(默認(rèn)數(shù)量,即最少數(shù)量)混狠,
   * 最大20線程(指定了線程池中的最大線程數(shù)量)岸霹,
   * 空閑時(shí)間0秒(當(dāng)線程池梳理超過核心數(shù)量時(shí),多余的空閑時(shí)間的存活時(shí)間将饺,即超過核心線程數(shù)量的空閑線程贡避,在多長(zhǎng)時(shí)間內(nèi),會(huì)被銷毀)予弧,
   * 等待隊(duì)列長(zhǎng)度1024刮吧,
   * 線程名稱[MXR-Task-%d],方便回溯,
   * 拒絕策略:當(dāng)任務(wù)隊(duì)列已滿掖蛤,拋出RejectedExecutionException
   * 異常杀捻。
   */
  private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 20, 0L,
      TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024)
      , new ThreadFactoryBuilder().setNameFormat("My-Task-%d").build()
      , new AbortPolicy()
  );

我們看到,ThreadPoolExecutor 也就是線程池有 7 個(gè)參數(shù)蚓庭,我們一起來好好看看:

  1. corePoolSize 線程池中核心線程數(shù)量
  2. maximumPoolSize 最大線程數(shù)量
  3. keepAliveTime 空閑時(shí)間(當(dāng)線程池梳理超過核心數(shù)量時(shí)致讥,多余的空閑時(shí)間的存活時(shí)間,即超過核心線程數(shù)量的空閑線程器赞,在多長(zhǎng)時(shí)間內(nèi)垢袱,會(huì)被銷毀)
  4. unit 時(shí)間單位
  5. workQueue 當(dāng)核心線程工作已滿,需要存儲(chǔ)任務(wù)的隊(duì)列
  6. threadFactory 創(chuàng)建線程的工廠
  7. handler 當(dāng)隊(duì)列滿了之后的拒絕策略

前面幾個(gè)參數(shù)我們就不講了拳魁,很簡(jiǎn)單,主要是后面幾個(gè)參數(shù)撮弧,隊(duì)列潘懊,線程工廠,拒絕策略贿衍。

我們先看看隊(duì)列授舟,線程池默認(rèn)提供了 4 個(gè)隊(duì)列。

  1. 無界隊(duì)列: 默認(rèn)大小 int 最大值贸辈,因此可能會(huì)耗盡系統(tǒng)內(nèi)存释树,引起OOM,非常危險(xiǎn)擎淤。
  2. 直接提交的隊(duì)列 : 沒有容量奢啥,不會(huì)保存,直接創(chuàng)建新的線程嘴拢,因此需要設(shè)置很大的線程池?cái)?shù)桩盲。否則容易執(zhí)行拒絕策略,也很危險(xiǎn)席吴。
  3. 有界隊(duì)列:如果core滿了赌结,則存儲(chǔ)在隊(duì)列中捞蛋,如果core滿了且隊(duì)列滿了,則創(chuàng)建線程柬姚,直到maximumPoolSize 到了拟杉,如果隊(duì)列滿了且最大線程數(shù)已經(jīng)到了,則執(zhí)行拒絕策略量承。
  4. 優(yōu)先級(jí)隊(duì)列:按照優(yōu)先級(jí)執(zhí)行任務(wù)搬设。也可以設(shè)置大小。

樓主在自己的項(xiàng)目中使用了無界隊(duì)列宴合,但是設(shè)置了任務(wù)大小焕梅,1024。如果你的任務(wù)很多卦洽,建議分為多個(gè)線程池贞言。不要把雞蛋放在一個(gè)籃子里。

再看看拒絕策略阀蒂,什么是拒絕策略呢该窗?當(dāng)隊(duì)列滿了,如何處理那些仍然提交的任務(wù)蚤霞。JDK 默認(rèn)有4種策略酗失。

  1. AbortPolicy :直接拋出異常,阻止系統(tǒng)正常工作.
  2. CallerRunsPolicy : 只要線程池未關(guān)閉昧绣,該策略直接在調(diào)用者線程中规肴,運(yùn)行當(dāng)前被丟棄的任務(wù)。顯然這樣做不會(huì)真的丟棄任務(wù)夜畴,但是拖刃,任務(wù)提交線程的性能極有可能會(huì)急劇下降。
  3. DiscardOldestPolicy: 該策略將丟棄最老的一個(gè)請(qǐng)求贪绘,也就是即將被執(zhí)行的一個(gè)任務(wù)兑牡,并嘗試再次提交當(dāng)前任務(wù).
  4. DiscardPolicy: 該策略默默地丟棄無法處理的任務(wù),不予任何處理税灌,如果允許任務(wù)丟失均函,我覺得這是最好的方案.

當(dāng)然,如果你不滿意JDK提供的拒絕策略菱涤,可以自己實(shí)現(xiàn)苞也,只需要實(shí)現(xiàn) RejectedExecutionHandler 接口,并重寫 rejectedExecution 方法即可粘秆。

最后墩朦,線程工廠,線程池的所有線程都由線程工廠來創(chuàng)建翻擒,而默認(rèn)的線程工廠太過單一氓涣,我們看看默認(rèn)的線程工廠是如何創(chuàng)建線程的:

/**
     * The default thread factory
     */
    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

可以看到牛哺,線程名稱為 pool- + 線程池編號(hào) + -thread- + 線程編號(hào) 。設(shè)置為非守護(hù)線程劳吠。優(yōu)先級(jí)為默認(rèn)引润。

如果我們想修改名稱呢?對(duì)痒玩,實(shí)現(xiàn) ThreadFactory 接口淳附,重寫 newThread 方法即可。但是已經(jīng)有人造好輪子了蠢古, 比如我們的例子中使用的 google 的 guaua 提供的 ThreadFactoryBuilder 工廠奴曙。可以自定義線程名稱草讶,是否守護(hù)洽糟,優(yōu)先級(jí),異常處理等等堕战,功能強(qiáng)大坤溃。

3. 如何擴(kuò)展線程池

那么我們能擴(kuò)展線程池的功能嗎?比如記錄線程任務(wù)的執(zhí)行時(shí)間嘱丢。實(shí)際上薪介,JDK 的線程池已經(jīng)為我們預(yù)留的接口,在線程池核心方法中越驻,有2 個(gè)方法是空的汁政,就是給我們預(yù)留的记劈。還有一個(gè)線程池退出時(shí)會(huì)調(diào)用的方法抠蚣。我們看看例子:

/**
 * 如何擴(kuò)展線程池履澳,重寫 beforeExecute, afterExecute, terminated 方法距贷,這三個(gè)方法默認(rèn)是空的忠蝗。
 *
 * 可以監(jiān)控每個(gè)線程任務(wù)執(zhí)行的開始和結(jié)束時(shí)間阁最,或者自定義一些增強(qiáng)速种。
 *
 * 在 Worker 的 runWork 方法中配阵,會(huì)調(diào)用這些方法
 */
public class ExtendThreadPoolDemo {

  static class MyTask implements Runnable {

    String name;

    public MyTask(String name) {
      this.name = name;
    }

    @Override
    public void run() {
      System.out
          .println("正在執(zhí)行:Thread ID:" + Thread.currentThread().getId() + ", Task Name = " + name);
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }


  public static void main(String[] args) throws InterruptedException {
    ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<>()) {
      @Override
      protected void beforeExecute(Thread t, Runnable r) {
        System.out.println("準(zhǔn)備執(zhí)行:" + ((MyTask) r).name);
      }

      @Override
      protected void afterExecute(Runnable r, Throwable t) {
        System.out.println("執(zhí)行完成: " + ((MyTask) r).name);
      }

      @Override
      protected void terminated() {
        System.out.println("線程池退出");
      }
    };

    for (int i = 0; i < 5; i++) {
      MyTask myTask = new MyTask("TASK-GEYM-" + i);
      es.execute(myTask);
      Thread.sleep(10);

    }

    es.shutdown();
  }

}

我們重寫了 beforeExecute 方法救拉,也就是執(zhí)行任務(wù)之前會(huì)調(diào)用該方法亿絮,而 afterExecute 方法則是在任務(wù)執(zhí)行完畢后會(huì)調(diào)用該方法拂铡。還有一個(gè) terminated 方法斗锭,在線程池退出時(shí)會(huì)調(diào)用該方法岖是。執(zhí)行結(jié)果是什么呢实苞?

可以看到聪轿,每個(gè)任務(wù)執(zhí)行前后都會(huì)調(diào)用 before 和 after 方法猾浦。相當(dāng)于執(zhí)行了一個(gè)切面音瓷。而在調(diào)用 shutdown 方法后則會(huì)調(diào)用 terminated 方法绳慎。

4. 如何優(yōu)化線程池的異常信息

如何優(yōu)化線程池的異常信息杏愤? 在說這個(gè)問題之前珊楼,我們先說一個(gè)不容易發(fā)現(xiàn)的bug:

看代碼:

  public static void main(String[] args) throws ExecutionException, InterruptedException {

    ThreadPoolExecutor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0L,
        TimeUnit.MILLISECONDS, new SynchronousQueue<>());

    for (int i = 0; i < 5; i++) {
      executor.submit(new DivTask(100, i));
    }


  }


  static class DivTask implements Runnable {
    int a, b;

    public DivTask(int a, int b) {
      this.a = a;
      this.b = b;
    }

    @Override
    public void run() {
      double re = a / b;
      System.out.println(re);
    }
  }

執(zhí)行結(jié)果:

注意:只有4個(gè)結(jié)果邓了,其中一個(gè)結(jié)果被吞沒了骗炉,并且沒有任何信息蛇受。為什么呢?如果仔細(xì)看代碼乍丈,會(huì)發(fā)現(xiàn)轻专,在進(jìn)行 100 / 0 的時(shí)候肯定會(huì)報(bào)錯(cuò)的请垛,但是卻沒有報(bào)錯(cuò)信息,令人頭痛亚兄,為什么呢审胚?實(shí)際上颓影,如果你使用 execute 方法則會(huì)打印錯(cuò)誤信息懒鉴,當(dāng)你使用 submit 方法卻沒有調(diào)用它的get 方法璃俗,異常將會(huì)被吞沒城豁,因?yàn)槌牵绻l(fā)生了異常间聊,異常是作為返回值返回的哎榴。

怎么辦呢尚蝌?我們當(dāng)然可以使用 execute 方法飘言,但是我們可以有另一種方式:重寫 submit 方法热凹,樓主寫了一個(gè)例子般妙,大家看一下:

  static class TraceThreadPoolExecutor extends ThreadPoolExecutor {

    public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
        TimeUnit unit, BlockingQueue<Runnable> workQueue) {
      super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    public void execute(Runnable command) {
//      super.execute(command);
      super.execute(wrap(command, clientTrace(), Thread.currentThread().getName()));
    }

    @Override
    public Future<?> submit(Runnable task) {
//      return super.submit(task);
      return super.submit(wrap(task, clientTrace(), Thread.currentThread().getName()));
    }

    private Exception clientTrace() {
      return new Exception("Client stack trace");
    }


    private Runnable wrap(final Runnable task, final Exception clientStack,
        String clientThreaName) {
      return new Runnable() {
        @Override
        public void run() {
          try {
            task.run();
          } catch (Exception e) {
            e.printStackTrace();
            clientStack.printStackTrace();
            throw e;
          }
        }
      };
    }
  }

我們重寫了 submit 方法,封裝了異常信息苫拍,如果發(fā)生了異常绒极,將會(huì)打印堆棧信息榔袋。我們看看使用重寫后的線程池后的結(jié)果是什么铡俐?

從結(jié)果中吏够,我們清楚的看到了錯(cuò)誤信息的原因:by zero锅知!并且堆棧信息明確,方便排錯(cuò)侣姆。優(yōu)化了默認(rèn)線程池的策略。

5. 如何設(shè)計(jì)線程池中的線程數(shù)量

線程池的大小對(duì)系統(tǒng)的性能有一定的影響沉噩,過大或者過小的線程數(shù)量都無法發(fā)揮最優(yōu)的系統(tǒng)性能捺宗,但是線程池大小的確定也不需要做的非常精確。因?yàn)橹灰苊鈽O大和極小兩種情況川蒙,線程池的大小對(duì)性能的影響都不會(huì)影響太大蚜厉,一般來說,確定線程池的大小需要考慮CPU數(shù)量畜眨,內(nèi)存大小等因素昼牛,在《Java Concurrency in Practice》 書中給出了一個(gè)估算線程池大小的經(jīng)驗(yàn)公式:

公式還是有點(diǎn)復(fù)雜的,簡(jiǎn)單來說康聂,就是如果你是CPU密集型運(yùn)算贰健,那么線程數(shù)量和CPU核心數(shù)相同就好氓侧,避免了大量無用的切換線程上下文独郎,如果你是IO密集型的話,需要大量等待,那么線程數(shù)可以設(shè)置的多一些,比如CPU核心乘以2.

至于如何獲取 CPU 核心數(shù),Java 提供了一個(gè)方法:

Runtime.getRuntime().availableProcessors();

返回了CPU的核心數(shù)量。

總結(jié)

好了拍皮,到這里爹橱,我們已經(jīng)對(duì)如何使用線程池有了一個(gè)認(rèn)識(shí)冯键,這里,樓主建議大家手動(dòng)創(chuàng)建線程池枉昏,這樣對(duì)線程池中的各個(gè)參數(shù)可以有精準(zhǔn)的了解晰奖,在對(duì)系統(tǒng)進(jìn)行排錯(cuò)或者調(diào)優(yōu)的時(shí)候有好處蛆楞。比如設(shè)置核心線程數(shù)多少合適,最大線程數(shù),拒絕策略,線程工廠冀瓦,隊(duì)列的大小和類型等等尼啡,也可以是G家的線程工廠自定義線程书聚。

下一篇,我們將深入源碼,看看JDK 的線程池是如何實(shí)現(xiàn)的冲九。因此灭贷,先熟悉線程池的使用吧L友印;苹尽!

good luckJ鞘取@鍪痢坠非!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末概龄,一起剝皮案震驚了整個(gè)濱河市救欧,隨后出現(xiàn)的幾起案子衰粹,更是在濱河造成了極大的恐慌,老刑警劉巖笆怠,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铝耻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)瓢捉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門频丘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泡态,你說我怎么就攤上這事搂漠。” “怎么了某弦?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵桐汤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我靶壮,道長(zhǎng)怔毛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任腾降,我火速辦了婚禮拣度,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蜂莉。我一直安慰自己蜡娶,他們只是感情好混卵,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布映穗。 她就那樣靜靜地躺著,像睡著了一般幕随。 火紅的嫁衣襯著肌膚如雪蚁滋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天赘淮,我揣著相機(jī)與錄音辕录,去河邊找鬼。 笑死梢卸,一個(gè)胖子當(dāng)著我的面吹牛走诞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛤高,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼蚣旱,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了戴陡?” 一聲冷哼從身側(cè)響起塞绿,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恤批,沒想到半個(gè)月后异吻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡喜庞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年诀浪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棋返。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雷猪,死狀恐怖懊昨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情春宣,我是刑警寧澤酵颁,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站月帝,受9級(jí)特大地震影響躏惋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嚷辅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一簿姨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧簸搞,春花似錦扁位、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至寺擂,卻和暖如春暇务,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怔软。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工垦细, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挡逼。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓括改,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親家坎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嘱能,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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