线程池-执行机制ThreadPoolExecutor官方文档

1.接口和抽象类的说明

1.1 Executor

An object that executes submitted Runnable tasks. This interface 
provides a way of decoupling task submission from the 
mechanics of how each task will be run, including details of 
thread use, scheduling, etc. An Executor is normally used instead 
of explicitly creating threads. For example, rather than invoking 
new Thread(new(RunnableTask())).start() for each of a set of 
tasks, you might use

执行提交的Runnable任务。此接口提供了将任务提交与任务运行机制(包括线程使用、调度等)分离的方法。通常使用Executor而不是显式创建线程。

 Executor executor = anExecutor;
 executor.execute(new RunnableTask1());
 executor.execute(new RunnableTask2());

1.2 ExecutorService

An Executor that provides methods to manage termination and 
methods that can produce a Future for tracking progress of one 
or more asynchronous tasks.

An ExecutorService can be shut down, which will cause it to 
reject new tasks. Two different methods are provided for shutting 
down an ExecutorService. The shutdown() method will allow 
previously submitted tasks to execute before terminating, while 
the shutdownNow() method prevents waiting tasks from starting 
and attempts to stop currently executing tasks. Upon termination, 
an executor has no tasks actively executing, no tasks awaiting 
execution, and no new tasks can be submitted. An unused 
ExecutorService should be shut down to allow reclamation of its 
resources.

Method submit extends base method 
Executor.execute(Runnable) by creating and returning a Future 
that can be used to cancel execution and/or wait for completion. 
Methods invokeAny and invokeAll perform the most commonly 
useful forms of bulk execution, executing a collection of tasks and 
then waiting for at least one, or all, to complete. (Class 
ExecutorCompletionService can be used to write customized 
variants of these methods.)

The Executors class provides factory methods for the executor 
services provided in this package.

提供更多方法的Executor:管理终止的方法,生成跟踪一个或多个异步任务进度的Future的方法。

ExecutorService可关闭,关闭后会拒绝新任务。两种方法关闭:

  • shutdown()方法将允许先前提交的任务继续执行
  • shutdownNow()将阻止等待任务启动并尝试停止当前正在执行的任务。

终止时,没有任何正在执行的任务,没有等待执行的任务,也没有任何新任务可提交。应关闭未使用的ExecutorService以允许回收器资源。

submit方法扩展自Executor.execute(Runnable),可以创建和返回一个Future以取消执行或等待完成。invokeAny和invokeAll批量执行任务,执行一组任务,然后等待至少一个或全部完成。(ExecutorCompletionService可用于对这些方法进行定制)。

如下是网络服务的框架,线程池中的线程为请求提供服务,使用预配置的Executors.newFixedThreadPool(int)工厂方法:

 class NetworkService implements Runnable {
   private final ServerSocket serverSocket;
   private final ExecutorService pool;

   public NetworkService(int port, int poolSize)
       throws IOException {
     serverSocket = new ServerSocket(port);
     pool = Executors.newFixedThreadPool(poolSize);
   }

   public void run() { // run the service
     try {
       for (;;) {
         pool.execute(new Handler(serverSocket.accept()));
       }
     } catch (IOException ex) {
       pool.shutdown();
     }
   }
 }

 class Handler implements Runnable {
   private final Socket socket;
   Handler(Socket socket) { this.socket = socket; }
   public void run() {
     // read and service request on socket
   }
 }

以下方法分两个阶段关闭ExecutorService,首先调用shutdown拒绝接收任务,然后在必要时调用shutdownNow以取消还在执行的任务:

 void shutdownAndAwaitTermination(ExecutorService pool) {
   pool.shutdown(); // Disable new tasks from being submitted
   try {
     // Wait a while for existing tasks to terminate
     if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
       pool.shutdownNow(); // Cancel currently executing tasks
       // Wait a while for tasks to respond to being cancelled
       if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
     }
   } catch (InterruptedException ie) {
     // (Re-)Cancel if current thread also interrupted
     pool.shutdownNow();
     // Preserve interrupt status
     Thread.currentThread().interrupt();
   }
 }

1.3 AbstractExecutorService

Provides default implementations of ExecutorService execution 
methods. This class implements the submit, invokeAny and 
invokeAll methods using a RunnableFuture returned by 
newTaskFor, which defaults to the FutureTask class provided in 
this package. For example, the implementation of 
submit(Runnable) creates an associated RunnableFuture that is 
executed and returned. Subclasses may override the newTaskFor 
methods to return RunnableFuture implementations other than 
FutureTask.

提供ExecutorService执行方法的默认实现。此类实现了submit、invokeAny及invokeAll方法,使用newTaskFor返回的RunnableFuture(默认是FutureTask)。例如submit(Runnable) 创建、执行并返回一个关联的RunnableFuture。子类可以覆盖newTaskFor 返回其他的RunnableFuture实现。

    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException {
        if (tasks == null)
            throw new NullPointerException();
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
        boolean done = false;
        try {
            for (Callable<T> t : tasks) {
                RunnableFuture<T> f = newTaskFor(t);
                futures.add(f);
                execute(f);
            }
            for (int i = 0, size = futures.size(); i < size; i++) {
                Future<T> f = futures.get(i);
                if (!f.isDone()) {
                    try {
                        f.get();
                    } catch (CancellationException ignore) {
                    } catch (ExecutionException ignore) {
                    }
                }
            }
            done = true;
            return futures;
        } finally {
            if (!done)
                for (int i = 0, size = futures.size(); i < size; i++)
                    futures.get(i).cancel(true);
        }
    }

例如,下面使用CustomTask代替FutureTask:

 public class CustomThreadPoolExecutor extends ThreadPoolExecutor {

   static class CustomTask<V> implements RunnableFuture<V> {...}

   protected <V> RunnableFuture<V> newTaskFor(Callable<V> c) {
       return new CustomTask<V>(c);
   }
   protected <V> RunnableFuture<V> newTaskFor(Runnable r, V v) {
       return new CustomTask<V>(r, v);
   }
   // ... add constructors, etc.
 }

2.ThreadPoolExecutor

An ExecutorService that executes each submitted task using one 
of possibly several pooled threads, normally configured using 
Executors factory methods.

Thread pools address two different problems: they usually 
provide improved performance when executing large numbers of 
asynchronous tasks, due to reduced per-task invocation 
overhead, and they provide a means of bounding and managing 
the resources, including threads, consumed when executing a 
collection of tasks. Each ThreadPoolExecutor also maintains 
some basic statistics, such as the number of completed tasks.

To be useful across a wide range of contexts, this class provides 
many adjustable parameters and extensibility hooks. However, 
programmers are urged to use the more convenient Executors 
factory methods Executors.newCachedThreadPool() (unbounded 
thread pool, with automatic thread reclamation), 
Executors.newFixedThreadPool(int) (fixed size thread pool) and 
Executors.newSingleThreadExecutor() (single background 
thread), that preconfigure settings for the most common usage 
scenarios. Otherwise, use the following guide when manually 
configuring and tuning this class:

线程池解决了两个不同的问题:

  • 由于减少了每个任务的调用开销,它们通常在执行大量异步任务时提供更高的性能
  • 并且它们提供了一种绑定和管理资源(包括线程)的方法,这些资源用于执行一组任务。每个ThreadPoolExecutor还维护一些基本统计信息,例如已完成任务的数量。

为了适用于各种上下文,该类提供了许多可调参数和可扩展性的钩子。但是,推荐程序员被使用更方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,具有自动线程回收),Executors.newFixedThreadPool(int)(固定大小线程池)和Executors.newSingleThreadExecutor()(单个线程)。否则,在手动配置和调整此类时,请参照以下指南:

2.1 Core and maximum pool sizes

A ThreadPoolExecutor will automatically adjust the pool size (see 
getPoolSize()) according to the bounds set by corePoolSize (see 
getCorePoolSize()) and maximumPoolSize (see 
getMaximumPoolSize()). When a new task is submitted in 
method execute(Runnable), and fewer than corePoolSize threads 
are running, a new thread is created to handle the request, even 
if other worker threads are idle. If there are more than 
corePoolSize but less than maximumPoolSize threads running, a 
new thread will be created only if the queue is full. By setting 
corePoolSize and maximumPoolSize the same, you create a 
fixed-size thread pool. By setting maximumPoolSize to an 
essentially unbounded value such as Integer.MAX_VALUE, you 
allow the pool to accommodate an arbitrary number of concurrent 
tasks. Most typically, core and maximum pool sizes are set only 
upon construction, but they may also be changed dynamically 
using setCorePoolSize(int) and setMaximumPoolSize(int).

ThreadPoolExecutor将根据corePoolSize(请参阅getCorePoolSize())和maximumPoolSize(请参阅getMaximumPoolSize())设置的边界自动调整池大小(请参阅getPoolSize())。

当在方法execute(Runnable)中提交新任务,并且运行的线程少于corePoolSize时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理该请求。

如果多于corePoolSize但少于maximumPoolSize线程在运行,则只有在队列已满时才会创建新线程。

通过设置相同的corePoolSize和maximumPoolSize,可以创建固定大小的线程池。 通过将maximumPoolSize设置为Integer.MAX_VALUE,可以允许池容纳任意数量的并发任务。 最典型的情况是,核心和最大池大小仅在构造时设置,但也可以使用setCorePoolSize(int)和setMaximumPoolSize(int)动态更改。

2.2 On-demand construction

By default, even core threads are initially created and started only 
when new tasks arrive, but this can be overridden dynamically 
using method prestartCoreThread() or prestartAllCoreThreads(). 
You probably want to prestart threads if you construct the pool 
with a non-empty queue.

默认情况下即使核心线程已经初始化并创建,但也只有在新任务到达时才启动。

可以通过使用方法prestartCoreThread() 和prestartAllCoreThreads()改变。当创建线程池的队列为非空,可能需要预启动线程。

2.3 Creating new threads

New threads are created using a ThreadFactory. If not otherwise 
specified, a Executors.defaultThreadFactory() is used, that 
creates threads to all be in the same ThreadGroup and with the 
same NORM_PRIORITY priority and non-daemon status. By 
supplying a different ThreadFactory, you can alter the thread's 
name, thread group, priority, daemon status, etc. If a 
ThreadFactory fails to create a thread when asked by returning 
null from newThread, the executor will continue, but might not be 
able to execute any tasks. Threads should possess the 
"modifyThread" RuntimePermission. If worker threads or other 
threads using the pool do not possess this permission, service 
may be degraded: configuration changes may not take effect in a 
timely manner, and a shutdown pool may remain in a state in 
which termination is possible but not completed.

使用ThreadFactory创建新线程。 如果没有另外指定,则使用Executors.defaultThreadFactory(),它创建的所有线程都在同一个ThreadGroup中并具有相同的NORM_PRIORITY优先级和非守护进程状态。 通过提供不同的ThreadFactory,您可以更改线程的名称、线程组、优先级、守护程序状态等。

如果ThreadFactory创建线程失败,executor会继续运行,但是可能不会执行任何任务。线程应该拥有"modifyThread" RuntimePermission。如果使用池的工作线程或其他线程没有该权限,服务可能会降级:配置更改可能无法及时生效,并且关闭的池可能保持为完全终止的状态。

2.4 Keep-alive times

If the pool currently has more than corePoolSize threads, excess 
threads will be terminated if they have been idle for more than the 
keepAliveTime (see getKeepAliveTime(TimeUnit)). This provides 
a means of reducing resource consumption when the pool is not 
being actively used. If the pool becomes more active later, new 
threads will be constructed. This parameter can also be changed 
dynamically using method setKeepAliveTime(long, TimeUnit). 
Using a value of Long.MAX_VALUE TimeUnit.NANOSECONDS 
effectively disables idle threads from ever terminating prior to 
shut down. By default, the keep-alive policy applies only when 
there are more than corePoolSize threads. But method 
allowCoreThreadTimeOut(boolean) can be used to apply this 
time-out policy to core threads as well, so long as the 
keepAliveTime value is non-zero.

如果池当前有多于corePoolSize数量的线程,超过的线程如果空闲时间超过keepAliveTime就会终止(see getKeepAliveTime(TimeUnit))。这提供了一种当池不是活跃使用情况下减少资源浪费的方法。如果池随后变得更活跃了,会创建新线程。该参数也可以动态进行修改(setKeepAliveTime(long, TimeUnit))。使用Long.MAX_VALUE TimeUnit.NANOSECONDS将使空闲线程不会终止。默认情况下,keep-alive策略仅适用于多于corePoolSize的线程,但是allowCoreThreadTimeOut(boolean)方法可将超时策略应用于核心线程,只要keepAliveTime值为非零。

2.5 Queuing

Any BlockingQueue may be used to transfer and hold submitted 
tasks. The use of this queue interacts with pool sizing:
* If fewer than corePoolSize threads are running, the Executor 
  always prefers adding a new thread rather than queuing.
* If corePoolSize or more threads are running, the Executor 
  always  prefers queuing a request rather than adding a new thread.
* If a request cannot be queued, a new thread is created unless 
  this would exceed maximumPoolSize, in which case, the task will 
  be rejected.

There are three general strategies for queuing:
1.Direct handoffs. A good default choice for a work queue is a 
  SynchronousQueue that hands off tasks to threads without 
  otherwise holding them. Here, an attempt to queue a task will fail 
  if no threads are immediately available to run it, so a new thread 
  will be constructed. This policy avoids lockups when handling 
  sets of requests that might have internal dependencies. Direct 
  handoffs generally require unbounded maximumPoolSizes to 
  avoid rejection of new submitted tasks. This in turn admits the 
  possibility of unbounded thread growth when commands continue 
  to arrive on average faster than they can be processed.

2.Unbounded queues. Using an unbounded queue (for example a 
  LinkedBlockingQueue without a predefined capacity) will cause 
  new tasks to wait in the queue when all corePoolSize threads are 
  busy. Thus, no more than corePoolSize threads will ever be 
  created. (And the value of the maximumPoolSize therefore 
  doesn't have any effect.) This may be appropriate when each 
  task is completely independent of others, so tasks cannot affect 
  each others execution; for example, in a web page server. While 
  this style of queuing can be useful in smoothing out transient 
  bursts of requests, it admits the possibility of unbounded work 
  queue growth when commands continue to arrive on average 
  faster than they can be processed.

3.Bounded queues. A bounded queue (for example, an 
  ArrayBlockingQueue) helps prevent resource exhaustion when 
  used with finite maximumPoolSizes, but can be more difficult to 
  tune and control. Queue sizes and maximum pool sizes may be 
  traded off for each other: Using large queues and small pools 
  minimizes CPU usage, OS resources, and context-switching 
  overhead, but can lead to artificially low throughput. If tasks 
  frequently block (for example if they are I/O bound), a system 
  may be able to schedule time for more threads than you 
  otherwise allow. Use of small queues generally requires larger 
  pool sizes, which keeps CPUs busier but may encounter 
  unacceptable scheduling overhead, which also decreases 
  throughput.

任何BlockingQueue都可用于传送和保存提交的任务。队列的使用与池大小相互作用:

  • 如果运行的线程少于corePoolSize,则Executor会添加新线程而不是排队。
  • 如果有corePoolSize或更多线程正在运行,则Executor会让人物排队请求而不是添加新线程。
  • 如果请求无法排队,则会创建一个新线程,除非线程数超过maximumPoolSize,在这种情况下,该任务将被拒绝。

排队有三种常规策略:

  • 直接交接。此种类型可以使用SynchronousQueue,它将任务交给线程而不另外保存它们。如果没有线程立即可用于运行人物,则尝试对任务进行排队将失败,因此将构造新线程。此策略在处理可能具有内部依赖性的请求集时避免了锁定。直接交接通常需要无限制的maximumPoolSizes以避免拒绝新提交的任务。当任务平均到达的速度快于它们被处理的速度时,会导致无限制的线程数量增长。
  • 无界队列。使用无界队列(例如未设置容量的LinkedBlockingQueue)会导致新任务进入队列等待,所有corePoolSize线程全部处于忙碌状态。因此,不会有多于corePoolSize数量的线程。(maximumPoolSize 没有任何意义)。当每个任务完全独立于其他任务时,这可能是适合的,因此任务不会影响彼此的执行。例如,网页服务器。虽然这种排队方式可以用于平滑请求的瞬时突发,但当命令继续以比处理更快的速度到达时,会导致工作队列无限制的增长。
  • 有界队列。有限队列(例如,ArrayBlockingQueue)在与maximumPoolSizes一起使用时有助于防止资源耗尽,但可能更难以调整和控制。队列大小和最大池大小选择是一种折中和权衡:
    1)使用大型队列和小型池最小化CPU使用率、OS资源和上下文切换开销,但可能导致人为的低吞吐量。如果任务经常阻塞(例如,如果它们是I / O绑定的),系统可能能够提供比您允许的更多线程的时间。
    2)使用小队列通常需要更大的池大小,这会使CPU更加繁忙,但可能会遇到不可接受的调度开销,这也会降低吞吐量。

2.6 Rejected tasks

New tasks submitted in method execute(Runnable) will be 
rejected when the Executor has been shut down, and also when 
the Executor uses finite bounds for both maximum threads and 
work queue capacity, and is saturated. In either case, the execute 
method invokes the 
RejectedExecutionHandler.rejectedExecution(Runnable, 
ThreadPoolExecutor) method of its RejectedExecutionHandler. 
Four predefined handler policies are provided:
1.In the default ThreadPoolExecutor.AbortPolicy, the handler 
  throws a runtime RejectedExecutionException upon rejection.
2.In ThreadPoolExecutor.CallerRunsPolicy, the thread that invokes 
  execute itself runs the task. This provides a simple feedback 
  control mechanism that will slow down the rate that new tasks are 
  submitted.
3.In ThreadPoolExecutor.DiscardPolicy, a task that cannot be 
  executed is simply dropped.
4.In ThreadPoolExecutor.DiscardOldestPolicy, if the executor is not 
  shut down, the task at the head of the work queue is dropped, 
  and then execution is retried (which can fail again, causing this to 
  be repeated.)

It is possible to define and use other kinds of 
RejectedExecutionHandler classes. Doing so requires some care 
especially when policies are designed to work only under 
particular capacity or queuing policies.

当Executor关闭或已经达到最大线程数量和队列已满时,将拒绝execute(Runnable)提交的新任务。execute方法会调用其RejectedExecutionHandler的RejectedExecutionHandler.rejectedExecution(Runnable,ThreadPoolExecutor)方法。提供了四种预定义的处理程序策:

  • 1)在默认的ThreadPoolExecutor.AbortPolicy中,处理程序在拒绝时抛出运行时RejectedExecutionException。
  • 2)在ThreadPoolExecutor.CallerRunsPolicy中,调用execute本身的线程会运行该任务。这提供了一种简单的反馈控制机制,可以降低新任务的提交速度。
  • 3)在ThreadPoolExecutor.DiscardPolicy中,简单地丢弃无法执行的任务。
  • 4)在ThreadPoolExecutor.DiscardOldestPolicy中,如果 executor未关闭,则会删除工作队列头部的任务,然后重试执行(可能会再次失败,导致重复执行此操作)。

可以定义和使用其他类型的RejectedExecutionHandler类。这样做需要一些小心,特别是当策略设计为仅在特定容量或排队策略下工作时。

2.7 Hook methods

This class provides protected overridable beforeExecute(Thread, 
Runnable) and afterExecute(Runnable, Throwable) methods that 
are called before and after execution of each task. These can be 
used to manipulate the execution environment; for example, 
reinitializing ThreadLocals, gathering statistics, or adding log 
entries. Additionally, method terminated() can be overridden to 
perform any special processing that needs to be done once the 
Executor has fully terminated.

If hook or callback methods throw exceptions, internal worker 
threads may in turn fail and abruptly terminate.

此类提供了受保护的可重写的beforeExecute(Thread, Runnable) 和 afterExecute(Runnable, Throwable),方法在每个任务执行前和之后进行调用。可用于操作执行环境,例如重新初始化ThreadLocals,收集统计信息或添加日志条目。另外,可以重写terminated()方法以执行当Executor完全终止后需要执行的任何特殊处理。

如果钩子或回调方法抛出异常,内部工作线程可能会失败并突然终止。

2.8 Queue maintenance

Method getQueue() allows access to the work queue for 
purposes of monitoring and debugging. Use of this method for 
any other purpose is strongly discouraged. Two supplied 
methods, remove(Runnable) and purge() are available to assist in 
storage reclamation when large numbers of queued tasks 
become cancelled.

方法getQueue()允许访问工作队列以进行监视和调试。强烈建议不要将此方法用于任何其他目的。当大量排队的任务被取消时,方法remove(Runnable)和purge()可用于协助存储回收。

2.9 Finalization

A pool that is no longer referenced in a program AND has no 
remaining threads will be shutdown automatically. If you would 
like to ensure that unreferenced pools are reclaimed even if users 
forget to call shutdown(), then you must arrange that unused 
threads eventually die, by setting appropriate keep-alive times, 
using a lower bound of zero core threads and/or setting 
allowCoreThreadTimeOut(boolean).

不再被引用并且没有剩余线程的线程池将自动关闭。如果希望确保即使用户忘记调用shutdown()也会回收未引用的池,那么必须确保未使用的线程最终会死亡。这可以通过设置合适的keep-alive时间、使用0的核心线程数,或者设置allowCoreThreadTimeOut(boolean)来实现。

2.10 示例

此类的大多数扩展都会重写一个或多个受保护的钩子方法。 例如,这是一个添加简单暂停/恢复功能的子类

 class PausableThreadPoolExecutor extends ThreadPoolExecutor {
   private boolean isPaused;
   private ReentrantLock pauseLock = new ReentrantLock();
   private Condition unpaused = pauseLock.newCondition();

   public PausableThreadPoolExecutor(...) { super(...); }

   protected void beforeExecute(Thread t, Runnable r) {
     super.beforeExecute(t, r);
     pauseLock.lock();
     try {
       while (isPaused) unpaused.await();
     } catch (InterruptedException ie) {
       t.interrupt();
     } finally {
       pauseLock.unlock();
     }
   }

   public void pause() {
     pauseLock.lock();
     try {
       isPaused = true;
     } finally {
       pauseLock.unlock();
     }
   }

   public void resume() {
     pauseLock.lock();
     try {
       isPaused = false;
       unpaused.signalAll();
     } finally {
       pauseLock.unlock();
     }
   }
 }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,306评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,657评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,928评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,688评论 0 220
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,105评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,024评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,159评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,937评论 0 212
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,689评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,851评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,325评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,651评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,364评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,192评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,985评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,154评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,955评论 2 279

推荐阅读更多精彩内容