Java并发编程的艺术

1. cpu通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一任务。但是,再切换之前会保存上一任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。
2. 多线程一定快吗?

不一定。并发执行累加操作不超过百万次时,速度比串行执行累加操作要慢。因为线程有创建和上下文切换的开销。

3. 如何减少上下文的切换?

无锁并发编程,CAS算法,使用最少线程和使用协程
无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些方法来避免锁,如将数据的id按照hash算法取模分段,不同的线程处理不同段的数据

CAS算法:Java的Atomic包使用CAS算法来更新数据,不需要加锁
使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态
协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换

4. 避免死锁的常见方法:

避免一个线程同时获取多个锁
避免一个线程在锁内同时占用多个资源,保证每个锁只占用一个资源
尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况

5. Volatile的定义与实现原理:

定义:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量地值是一致的。

实现原则:Lock前缀指令会引起处理器缓存回写到内存,一个处理器的缓存回写到内存会导致其他处理器的缓存无效

6. synchronized的实现原理与应用:

实现同步的基础:Java中的每一个对像都可以作为锁。具体表现为:
对于普通同步方法,锁时当前实例对象
对于静态同步方法,锁是当前类的class对象
对于同步方法块,锁是synchronized括号里配置的对象

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
实现原理:jvm基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步时使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,但是方法的同步同样可以使用这两个指令来实现。Monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之相连,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试得对象的锁。

7. Java对象头

Synchronized用的锁是存在Java对象头里的。如果对象是数组类型,虚拟机用3个字宽存储对象头,否则用2字宽存储对象头。

8. 锁的状态:

无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态
锁可以升级但不能降级,这种策略是为了提高获得锁和释放锁的效率

9. 偏向锁:

定义:它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。

偏向锁的实现:
获取过程:

  1. 访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01,确认为可偏向状态
  2. 如果为可偏向状态,则测试线程id是否指向当前线程。如果是,进入步骤5,否则进入步骤3
  3. 如果线程id并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程id设置为当前线程id,然后执行步骤5,如果竞争失败,执行步骤4
  4. 如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码
  5. 执行同步代码
    释放过程:
    偏向锁使用了一种等待竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码),它会首先咱潜艇拥有偏向锁的进程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位01)或轻量级锁(标志位00)的状态
10. 轻量级锁

轻量级锁时由偏向锁升级而来的,偏向锁运行在一个线程进入同步块的情况下,当dierge 线程加入锁争用的时候,偏向锁就会升级成轻量级锁
加锁:

  1. 代码进入同步快的时候,如果同步对象锁状态为无锁状态,虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Reocrd)的空间,用于存储对象目前的Mark Word的拷贝。
  2. 拷贝对象头中的Mark Word复制到锁记录中
  3. 拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指定的Lock Reocrd的指针,官方称为Displaced Mark Word并将Lock Reocrd里的owner指针指向object mark word。如果成功,执行步骤4,否则执行步骤5
  4. 线程拥有该对象的锁,并且对象Mark Word的锁标志位设置为00,表示此对象处于轻量级锁定状态
  5. 如果失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果时是则说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。斗则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为10,Mark Word中存储的就是指向重量级锁的指针,后面等待锁的线程也要进入阻塞状态。而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采取循环取获取锁的过程。

解锁:
会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功则表示没有竞争发生,如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。

11. 锁的优缺点对比:
12. 线程

定义:现在操作系统在运行一个程序时,会为其创建一个进程。现代操作系统调度的最小单元是线程,也叫轻量级进程,在一个进程里可以创建多个线程,这些线程都拥有各自的计数器,堆栈和局部变量等属性,并且能够访问共享的内存变量。

程序在各自的进程中进行:相互分离,各自独立执行,由操作系统来分配资源,比如内存,文件句柄,安全证书。进程h会通过一些原始的机制相互通信:socket,信号处理,共享内存,信号量。

线程优先级:在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1—10,线程构建的时候可以通过setPriority(int)方法来修改优先级,优先级高的线程分配时间片的数量要多余优先级第的线程。

线程的状态:
New 初始状态,线程被构建,但是还没有调用start()方法
Runnable 运行状态,java线程将操作系统中的就绪和运行两种状态笼统地称作“运行中”
Blocked 阻塞状态,表示线程阻塞于锁
Waiting 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)
Time_waiting 超时等待状态,该状态不等同于waiting,她是可以在指定地时间自行返回的
Terminated 终止状态,表示当前线程已经执行完毕

线程的状态变迁:

线程创建之后调用start()方法开始运行。当线程执行wait()方法之后,线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能返回运行状态,而超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会返回运行状态。当线程调用永不方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态。线程在执行Runnable的run()方法之后将会进入到终止状态。

(阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块时的状态,但是阻塞在java.concurrent包中Lock接口的线程状态确是等待状态,因为java.concurrent包中Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法)

13. Daemon线程

Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。可以通过调用Thread.setDaemon(true)将贤臣设置为Daemon线程

(Daemon属性需要在启动线程之前设置,不能在启动线程之后设置。Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块并不一定会执行,所以构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑)

14. 不建议使用suspend(),resume(),stop()方法完成线程暂停,恢复和终止工作的原因:

Suspend()方法:在调用后,县城不会释放已经占有的资源(比如锁),而是占着资源进入睡眠状态,这样容易引发死锁问题。同样stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给与线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下

15. 安全终止线程

中断操作时一种简便的线程间交互方式,而这种交互方法最适合用来取消或停止任务,除了中断以外,还可以利用一个boolean变量来控制是否需要停止任务来终止该线程。

16. 中断

中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。中断好比其他的线程对该线程打了个招呼,其他线程通过调用该线程的interrupt()方法对其进行中断操作。

17. Volatile和synchronized关键字

Java支持多个线程同时访问一个对象或者对象的成员变量。由于每个线程可以拥有这个变量的拷贝,所以程序在执行过程中,一个线程看到的变量并一定是最新的。
关键字volatile可以用来修饰字段(成员变量),告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新共享内存,它能保证所有线程对变量访问的可见性。

关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。

18. 等待/通知的相关方法

等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait
()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

注意点:

  1. 使用wait(),notify(),notifyAll()时需要先对调用对象加锁
  2. 调用wait()方法后,线程状态由running变成waiting,并将当前线程放置到对象的等待序列
  3. Notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifyAll()的线程释放锁之后,等待线程才有机会从wait()返回
  4. Notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由waiting变为blocked
  5. 从wait()方法返回的前提是获得了调用对象的锁
19. 管道输入/输出流

用于线程之间的数据传输,而传输的媒介为内存

具体实现:PipedOutStream,PipedInputStream,PipedReader,PipedWriter,前两种面向字节,后两种面向字符。

注意:对于piped类型的流,必须先要进行绑定,也就是调用connect()方法,如果没有将输入/输出流绑定起来(out.connect(in)),对于该流的访问将会抛出异常(IOException)。

20. Thread.join()的使用

如果一个线程A执行了Thread.join()语句,含义是:当前线程A等待thread线程终止之后,才从thread.join()返回。

21. ThreadLocal

线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。
可以通过set(T)方法来设置值,在房前线程下再通过get()方法获取到原先设置的值

22. 等待/通知经典范式:

加锁,条件循环,处理逻辑

23. 超时等待模式:

在等待/通知范式基础上增加了超时控制,即使方法执行时间长,也不会永久阻塞调用者,而是会按照调用者的要求按时返回

24. Lock接口

锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁)。Lock接口提供了与synchronized关键字类似的同步功能,只是在使用时需要显示的获取和释放锁。虽然它缺少了隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性

25. ConsurrentHashMap:线程安全又高效的hashMap

线程允许程序控制流的多重分支同时存在于一个进程。它们共享进程范围内的资源,比如内存和文件句柄,但是每一个线程有其自己的程序计数器,栈和本地变量。
恰当的使用线程,可以降低开发和维护的开销,能够提高复杂应用的性能。
当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方代码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。

26. Java中合理使用线程池的好处

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统地稳定性,使用线程池可以进行统一分配,调优和监控。

27. 线程池的实现原理

当提交一个新任务到线程池时,线程池的处理流程:
1) 线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程
2) 线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
3) 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
ThreadPoolExecutor执行execute方法分下面4种情况:
1) 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务。
2) 如果运行的线程等于或多余corePoolSize,则将任务加入BlockingQuene。
3) 如果无法将任务加入BlockingQuene(队列已满),则将创建新的线程来处理任务。
4) 如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法
5)

竞争条件:当计算的正确性依赖于运行中相关的时序或者多线程的交替时,会产生竞争条件。最常见的一种竞争条件是:检查再运行,使用一个潜在的过期值作为决定下一步操作的依据。
线程:

线程指在程序执行过程中,能够执行程序代码的一个执行单位。每个程序至少都有一个线程,也就是程序本身。
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速。

线程和进程:

线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。每个线程又拥有单独的栈内存用来存储本地数据

在Java中实现线程:

Java.lang.Thread类的实例就是一个线程,但是需要调用java.lang.Runnable接口来执行,由于线程类本身就是调用的Runnable接口,所以你可以继承java.lang.Thread类或者直接调用Runnable接口来重写run()方法来实现线程

用Runnable还是Thread:

Java不支持类的多重继承,但允许调用多个接口,如果你需要继承其他类,当然是调用Runnable接口

Thread类中的start()和run()的区别:

Start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当调用run()方法时,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程

Java中的Runnable和Callable的不用:

Runnable和Callable都代表那些要在不同的线程中执行的任务。它们的主要区别时Callable的call()方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能

Java中CyclicBarrier和CountDownLatch的不同:

CyclicBarrier和CountDownLatch都可以用来让一组线程等待其他线程。与CyclicBarric不同的是,countdownLatch不能重新使用

Java中的volatile变量:

Volatile是一个特殊的修饰符,只有成员变量才能使用它,在Java并发程序缺少同步类的情况下,多线程对成员变量的操作就是对其他线程是透明的。Volatile变量可以保证下一个读取操作会在前一个写操作之后发生

线程安全:

如果代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然集合类有两组:线程安全和非线程安全。Vector是用同步方法来实现线程安全的,而arraylist不是线程安全的
Java中的竞态条件
竞态条件会导致程序在并发情况下出现一些bugs。多线程对一些资源的竞争的时候就会产生竞态条件,如果首先要执行的程序竞争失败排到后面执行了,那么整个程序就会出现一些不确定的bugs。这中bugs很难发现而且会重复出现,因为线程间的随机竞争。

Java中停止一个线程:

当run()或者call()方法执行完的时候线程会自动结束,如果要手动结束一个线程,可以用volatile布尔变量来退出run()方法的循环或者是取消任务来中断线程

一个线程运行时发生异常会怎样:

如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler是用来处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个为捕获异常将造成线程中断的时候jvm会使用Thread.UncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法进行处理。

如何在两个线程间共享数据:

通过共享对象来实现,或者是使用像阻塞队列这样并发的数据结构

Java中notify和notifyAll的区别:

Notify()方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有用武之地。而notifyAll()唤醒所有线程并允许它们争夺锁确保了至少有一个线程能继续运行

Wait,notify和notifyAll方法不在thread类里面:

Java提供的锁是对象级而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在thread类中,线程等待的是哪个锁就不明显了。简单的来说,由于wait,notify和notifyAll都是锁级别的操作,所及把它们定义在object类中因为锁属于对象

ThreadLocal变量:

ThreadLocal是Java里一种特殊的变量。每个线程都有一个Threadlocal就是每个线程都有自己独立的一个变量,竞争条件被彻底消除了。它是创建代价高昂的对象获取线程安全的好办法。可以用ThreadLocal让SimpleDateFormat变成线程安全的。

Wait和notify方法要在同步块中调用:

Java api强制要求这样做,如果不这么做,代码会抛出IlegalMonitorStateException异常;为了避免wait和notify之间产生竞态条件

Java多线程中的死锁:

死锁是两个或两个以上的进程在执行过程中,因争夺资源造成的一种相互等待的现象,若无外力作用,它们将无法推进下去,死锁会让你的程序挂起无法执行任务。
死锁满足以下条件:
互斥条件:一个资源每次只能被一个进程使用
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:进程获得的资源,在未使用完之前,不能强行剥夺
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
避免死锁的最简单的方法就是组织循环等待条件,将系统中所有的资源设置标志位,排序,规定所有的进程申请资源必须以一定的顺序做操作来避免死锁

Java中活锁和死锁的区别:

活锁和死锁类似,不同之处在于处于活锁的进程或线程的状态是不断改变的,活锁可以认为时一种特殊的饥饿。简单来说就是,活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行

检测一个线程是否拥有锁:

Java.lang.Thread中的holdLock(),它返回true,表示当前线程拥有某个具体对象的锁

Jvm中哪个参数是用来控制线程的栈堆大小的:

-Xss参数用来控制线程的堆栈大小

Thread类中的yield方法:

Yield方法可以暂停当前正在执行的线程对象,让其他有享用优先级的线程执行。它是一个静态方法而且只保证当前线程放弃cpu占用而不能保证使其它线程一定能占用cpu,执行yield()的线程有可能在进入到暂停状态后马上又被执行

T1,T2,T3三个线程如何确保它们按顺序执行:

可以使用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序,应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。

如何强制启动一个线程?

无法强制启动,它是被线程调度器控制的

Java多线程中调用wait()和sleep()方法有什么不同?

Wait方法用于线程间通信,如果等待条件为真或其他线程被唤醒时它会释放锁,而sleep方法仅仅释放cpu资源或者让当前线程停止一段时间,但不会释放锁。

Sleep和wait的区别:

Sleep时使线程停止一段时间的方法,在sleep时间间隔期满后,线程不一定立即恢复执行。因为在那个时刻,其他线程可能正在运行,而且没有被调度为放弃执行,除非醒来的线程具有更高的优先级,或者正在运行的线程因为其他原因被阻塞
Wait:当线程交互时,如果线程堆一个对象x发出一个wait调用,该线程会暂停执行,被调对象进入等待状态,直到被唤醒或等待时间到。

同步和异步:

如果数据将在线程间共享,例如正在些的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这次额数据就是共享数据,必须进行同步存取。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回,就应该使用异步编程。在很多情况下采取异步更有效率

Synchronized和Java.util.concurrent.locks.Lock的异同:

相同点:Lock能完成synchronized所实现的所有功能。
不同点:Lock有比Synchronized更景区的线程语义和更好的性能。Synchronized会自动释放锁,lock要求手工释放,并且必须在finally从句中释放。

串行化:

概念:对象寿命随着生成该对象的程序终止而终止。有时候,可能需要将对象的状态保存下来,在需要时再将对象恢复。对象的这种能记录自己的状态一遍将来再生的能力叫做对象的持续化。对象通过描述自己状态的数值来记录自己,这个过程叫做对象串行化/串行化的主要任务是写出对象实例变量的数值。如果变量使另一对象的引用,则引用的对象也要串行化。
在Java.io包中,接口serializable用来作为实现对象串行化的工具,只有实现了serializable的类的对象才可以被串行化。
Transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分

如果你提交任务时,线程池队列已满,会发生什么?

如果一个任务不能被调度执行,那么submit()方法将会抛出RejectedExecutionException异常

进程和线程的内存结构不同
进程之间是不能共享内存的,而线程可以

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

推荐阅读更多精彩内容