设计模式 - 单例模式(详解)看看和你理解的是否一样?

一、概述

单例模式是设计模式中相对简单且非常常见的一种设计模式,但是同时也是非常经典的高频面试题,相信还是有很多人在面试时会挂在这里。本篇文章主要针对单例模式做一个回顾,记录单例模式的应用场景、常见写法、针对线程安全进行调试(看得见的线程)以及总结。相信大家看完这篇文章之后,对单例模式有一个非常深刻的认识。

文章中按照常见的单例模式的写法,由浅入深进行讲解记录;以及指出该写法的不足,从而进行演进改造。

秉承废话少说的原则,我们下面快速开始

二、定义

单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。

单例模式是创建型模式。

三、应用场景

  1. 生活中的单例:例如,国家主席、公司 CEO、部门经理等。
  2. Java世界中:ServletContextServletContextConfig 等;
  3. Spring 框架应用中:ApplicationContext、数据库的连接池也都是单例形式。

四、常见的单例模式写法

单例模式主要有:饿汉式单例、懒汉式单例(线程不安全型、线程安全型、双重检查锁类型、静态内部类类型)、注册式(登记式)单例(枚举式单例、容器式单例)、ThreadLocal线程单例

下面我们来看看各种模式的写法。

1、饿汉式单例

饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题。

Spring 中 IOC 容器 ApplicationContext 就是典型的饿汉式单例

优缺点

优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。

缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅坑不拉屎。

写法

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午9:26
 */
public class HungrySingleton {
    // 1.私有化构造器
    private HungrySingleton (){}
    // 2.在类的内部创建自行实例
    private static final HungrySingleton instance = new HungrySingleton();
    // 3.提供获取唯一实例的方法(全局访问点)
    public static HungrySingleton getInstance(){
        return instance;
    }
}

还有另外一种写法,利用静态代码块的机制:

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午10:46
 */
public class HungryStaticSingleton {
    // 1. 私有化构造器
    private HungryStaticSingleton(){}

    // 2. 实例变量
    private static final HungryStaticSingleton instance;

    // 3. 在静态代码块中实例化
    static {
        instance = new HungryStaticSingleton();
    }

    // 4. 提供获取实例方法
    public static HungryStaticSingleton getInstance(){
        return instance;
    }
}

测试代码,我们创建 10 个线程(具体线程发令枪 ConcurrentExecutor 在文末源码中获取):

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午11:17
 */
public class HungrySingletonTest {
    @Test
    public void test() {
        try {
            ConcurrentExecutor.execute(() -> {
                HungrySingleton instance = HungrySingleton.getInstance();
                System.out.println(Thread.currentThread().getName() + " : " + instance);
            }, 10, 10);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

测试结果:

pool-1-thread-6 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-9 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-10 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-7 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-8 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
...

可以看到,饿汉式每次获取实例都是同一个。

使用场景

这两种写法都非常的简单,也非常好理解,饿汉式适用在单例对象较少的情况。

下面我们来看性能更优的写法——懒汉式单例。


2、懒汉式单例

懒汉式单例的特点是:被外部类调用的时候内部类才会加载。

懒汉式单例可以分为下面这几种写法来。

简单懒汉式(线程不安全)

这是懒汉式单例的简单写法

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午10:55
 */
public class LazySimpleSingleton {
    private LazySimpleSingleton(){}
    private static LazySimpleSingleton instance = null;

    public static LazySimpleSingleton getInstance(){
        if (instance == null) {
            instance = new LazySimpleSingleton();
        }
        return instance;
    }
}

我们创建一个多线程来测试一下,是否线程安全:

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午11:12
 */
public class LazySimpleSingletonTest {

    @Test
    public void test() {
        try {
            ConcurrentExecutor.execute(() -> {
                LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
                System.out.println(Thread.currentThread().getName() + " : " + instance);
            }, 5, 5);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

运行结果:

pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@748e48d0
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f

从测试结果来看,一定几率出现创建两个不同结果的情况,意味着上面的单例存在线程安全隐患。

至于为什么?由于篇幅问题,我们在后面一篇文章中利用测试工具进行详细的分析(这可能也是面试中面试官会问到的问题)。大家现在只需要知道简单的懒汉式会存在这么一个问题就行了。

简单懒汉式(线程安全)

通过对上面简单懒汉式单例的测试,我们知道存在线程安全隐患,那么,如何来避免或者解决呢?

我们都知道 java 中有一个synchronized可以来对共享资源进行加锁,保证在同一时刻只能有一个线程拿到该资源,其他线程只能等待。所以,我们对上面的简单懒汉式进行改造,给getInstance() 方法加上synchronized

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午10:55
 */
public class LazySimpleSyncSingleton {
    private LazySimpleSyncSingleton() {
    }

    private static LazySimpleSyncSingleton instance = null;

    public synchronized static LazySimpleSyncSingleton getInstance() {
        if (instance == null) {
            instance = new LazySimpleSyncSingleton();
        }
        return instance;
    }
}

然后使用发令枪进行测试:

@Test
public void testSync(){
    try {
        ConcurrentExecutor.execute(() -> {
            LazySimpleSyncSingleton instance = LazySimpleSyncSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + " : " + instance);
        }, 5, 5);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

进行多轮测试,并观察结果,发现能够获取同一个示例:

pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de

线程安全问题是解决了,但是,用synchronized加锁,在线程数量比较多情况下,如果CPU分配压力上升,会导致大批量线程出现阻塞,从而导致程序运行性能大幅下降。

那么,有没有一种更好的方式,既兼顾线程安全又提升程序性能呢?答案是肯定的。

我们来看双重检查锁的单例模式。

双重检查锁懒汉式

上面的线程安全方式的写法,synchronized锁是锁在 getInstance() 方法上,当多个线程过来拿资源的时候,其实需要拿的不是getInstance()这个方法,而是getInstance()方法里面的instance 实例对象,而如果这个实例对象一旦被初始化之后,多个线程到达时,就可以利用方法中的 if (instance == null) 去判断是否实例化,如果已经实例化了就直接返回,就没有必要再进行实例化一遍。所以对上面的代码进行改造:

第一次改造:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午2:03
 */
public class LazyDoubleCheckSingleton {
    private LazyDoubleCheckSingleton() {
    }

    private static LazyDoubleCheckSingleton instance = null;

    public static LazyDoubleCheckSingleton getInstance() {
        // 这里判断是为了过滤不必要的同步加锁,因为如果已经实例化了,就可以直接返回了
        if (instance == null) {
            // 如果未初始化,则对资源进行上锁保护,待实例化完成之后进行释放
            synchronized (LazyDoubleCheckSingleton.class) {
                instance = new LazyDoubleCheckSingleton();
            }
        }
        return instance;
    }
}

这种方法行不行?答案肯定是不行,代码中虽然是将同步锁添加到了实例化操作中,解决了每个线程由于同步锁的原因引起的阻塞,提高了性能;但是,这里会存在一个问题:

  • 线程X线程Y同时调用getInstance()方法,他们同时判断instance == null,得出的结果都是为null,所以进入了if代码块了
  • 此时线程X得到CPU的控制权 -> 进入同步代码块 -> 创建对象 -> 返回对象
  • 线程X执行完成了以后,释放了锁,然后线程Y得到了CPU的控制权。同样是 -> 进入同步代码块 -> 创建对象 -> 返回对象

所以我们明显可以分析出来:LazyDoubleCheckSingleton 类返回了不止一个实例!所以上面的代码是不行的!大家可以自行测试,我这里就不进行测试了!

我们再进行改造,经过分析,由于线程X已经实例化了对象,在线程Y再次进入的时候,我们再加一层判断不就可以解决 “这个” 问题吗?确实如此,来看代码:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午2:03
 */
public class LazyDoubleCheckSingleton {
    private LazyDoubleCheckSingleton() {
    }
    private static LazyDoubleCheckSingleton instance = null;
    public static LazyDoubleCheckSingleton getInstance() {
        // 这里判断是为了过滤不必要的同步加锁,因为如果已经实例化了,就可以直接返回了
        if (instance == null) {
            // 如果未初始化,则对资源进行上锁保护,待实例化完成之后进行释放(注意,可能多个线程会同时进入)
            synchronized (LazyDoubleCheckSingleton.class) {
                // 这里的if作用是:如果后面的进程在前面一个线程实例化完成之后拿到锁,进入这个代码块,
                // 显然,资源已经被实例化过了,所以需要进行判断过滤
                if (instance == null) {
                    instance = new LazyDoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

大家觉得经过这样改造是不是就完美了呢?在我们习惯性的“讲道理”的思维模式看来,好像确实没什么问题,但是,程序是计算机在执行;什么意思呢?

instance = new LazyDoubleCheckSingleton(); 这段代码执行的时候,计算机内部并非简单的一步操作,也就是非原子操作,在JVM中,这一行代码大概做了这么几件事情:

  1. instance 分配内存
  2. 调用 LazyDoubleCheckSingleton 的构造函数来初始化成员变量
  3. instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 中的即时编译器中存在指令重排序的优化;通俗的来说就是,上面的第二步和第三步的顺序是不能保证的,如果执行顺序是 1 -> 3 -> 2 那么在 3 执行完毕、2 未执行之前,被另外一个线程 A 抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程 A 会直接返回 instance,然后被程序调用,就会报错。

当然,这种情况是很难测试出来的,但是确实会存在这么一个问题,所以我们必须解决它,解决方式也很简单,就是 j 将 instance 加上 volatile 关键字。

所以相对较完美的实现方式是:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午2:03
 */
public class LazyDoubleCheckSingleton {
    private LazyDoubleCheckSingleton() {
    }

    private static volatile LazyDoubleCheckSingleton instance = null;

    public static LazyDoubleCheckSingleton getInstance() {
        // 这里判断是为了过滤不必要的同步加锁,因为如果已经实例化了,就可以直接返回了
        if (instance == null) {
            // 如果未初始化,则对资源进行上锁保护,待实例化完成之后进行释放(注意,可能多个线程会同时进入)
            synchronized (LazyDoubleCheckSingleton.class) {
                // 这里的if作用是:如果后面的进程在前面一个线程实例化完成之后拿到锁,进入这个代码块,
                // 显然,资源已经被实例化过了,所以需要进行判断过滤
                if (instance == null) {
                    instance = new LazyDoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

测试代码见文末说明

静态内部类懒汉式

上面的双重锁检查形式的单例,对于日常开发来说,确实够用了,但是在代码中使用synchronized关键字 ,总归是要上锁,上锁就会存在一个性能问题。难道就没有更好的方案吗?别说,还真有,我们从类初始化的角度来考虑,这就是这里所要说到的静态内部类的方式。

废话不多说,直接看代码:

/**
 *
 * @author eamon.zhang
 * @date 2019-09-30 下午2:55
 */
public class LazyInnerClassSingleton {

    private LazyInnerClassSingleton() {
    }

    // 注意关键字final,保证方法不被重写和重载
    public static final LazyInnerClassSingleton getInstance() {
        return LazyHolder.INSTANCE;
    }

    private static class LazyHolder {
        // 注意 final 关键字(保证不被修改)
        private static final LazyInnerClassSingleton INSTANCE = new LazyInnerClassSingleton();
    }
}

进行多线程测试:

pool-1-thread-9 : com.eamon.javadesignpatterns.singleton.lazy.inner.LazyInnerClassSingleton@88b7fa2
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.lazy.inner.LazyInnerClassSingleton@88b7fa2
pool-1-thread-6 : com.eamon.javadesignpatterns.singleton.lazy.inner.LazyInnerClassSingleton@88b7fa2
...

结果都是同一个对象实例。

结论

这种方式即解决了饿汉式的内存浪费问题,也解决了synchronized 所带来的性能问题

原理

利用的原理就是类的加载初始化顺序:

  1. 当类不被调用的时候,类的静态内部类是不会进行初始化的,这就避免了内存浪费问题;
  2. 当有方法调用 getInstance()方法时,会先初始化静态内部类,而静态内部类中的成员变量是 final 的,所以即便是多线程,其成员变量是不会被修改的,所以就解决了添加 synchronized 所带来的性能问题

首先感谢也恭喜大家能够看到这里,因为我想告诉你,上面所有的单例模式似乎还存在一点小问题 —— 暴力破坏。解决这一问题的方式就是下面提到的枚举类型单例。

至于缘由和为何枚举能够解决这个问题,同样,篇幅原因,我将在后面单独开一篇文章来说明。


下面我们先来讲讲注册式单例。

3、注册式(登记式)单例

注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。

注册式单例有两种写法:一种为容器缓存,一种为枚举登记。

先来看枚举式单例的写法。

枚举单例

废话少说,直接看代码,我们先创建EnumSingleton 类:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午3:42
 */
public enum EnumSingleton {
    INSTANCE;

    private Object instance;

    EnumSingleton() {
        instance = new EnumResource();
    }

    public Object getInstance() {
        return instance;
    }

}

来看测试代码:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午3:47
 */
public class EnumSingletonTest {

    @Test
    public void test() {
        try {
            ConcurrentExecutor.execute(() -> {
                EnumSingleton instance = EnumSingleton.INSTANCE;
                System.out.println(instance.getInstance());
            }, 10, 10);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

测试结果:

com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7

结果都一样,说明枚举类单例是线程安全的,且是不可破坏的;在 JDK 枚举的语法特殊性,以及反射也为枚举保驾护航,让枚举式单例成为一种比较优雅的实现。

枚举类单例也是《Effective Java》中所建议使用的。

容器式单例

注册式单例还有另外一种写法,利用容器缓存,直接来看代码:

创建ContainerSingleton类:

/**
 * @author EamonZzz
 * @date 2019-10-06 18:28
 */
public class ContainerSingleton {
    private ContainerSingleton() {
    }

    private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();

    public static Object getBean(String className) {
        synchronized (ioc) {
            if (!ioc.containsKey(className)) {
                Object object = null;
                try {
                    object = Class.forName(className).newInstance();
                    ioc.put(className, object);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return object;
            } else {
                return ioc.get(className);
            }
        }
    }
}

测试代码:

@Test
    public void test() {
        try {
            ConcurrentExecutor.execute(() -> {
                Object bean = ContainerSingleton
                        .getBean("com.eamon.javadesignpatterns.singleton.container.Resource");
                System.out.println(bean);
            }, 5, 5);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

测试结果:

com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f
com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f
com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f
com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f
com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f

容器式写法适用于创建实例非常多的情况,便于管理。但是,是非线程安全的。

其实 Spring 中也有相关容器史丹利的实现代码,比如 AbstractAutowireCapableBeanFactory 接口

至此,注册式单例介绍完毕。


五、拓展

ThreadLocal 线程单例

ThreadLocal 不能保证其创建的对象是唯一的,但是能保证在单个线程中是唯一的,并且在单个线程中是天生的线程安全。

看代码:

/**
 * @author EamonZzz
 * @date 2019-10-06 21:40
 */
public class ThreadLocalSingleton {
    private ThreadLocalSingleton() {
    }

    private static final ThreadLocal<ThreadLocalSingleton> instance = ThreadLocal.withInitial(ThreadLocalSingleton::new);

    public static ThreadLocalSingleton getInstance() {
        return instance.get();
    }
}

测试程序:

@Test
public void test() {
    System.out.println("-------------- 单线程 start ---------");
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println("-------------- 单线程 end ---------");
    System.out.println("-------------- 多线程 start ---------");
    try {
        ConcurrentExecutor.execute(() -> {
            ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + " : " + singleton);

        }, 5, 5);
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("-------------- 多线程 end ---------");
}

测试结果:

-------------- 单线程 start ---------
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
-------------- 单线程 end ---------
-------------- 多线程 start ---------
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@2f540d92
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@3ef7ab4e
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@604ffe2a
pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@50f41c9f
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@40821a7a
-------------- 多线程 end ---------

从测试结果来看,我们不难发现,在主线程中无论调用多少次,获得到的实例都是同一个;在多线程环境下,每个线程获取到了不同的实例。

所以,在单线程环境中,ThreadLocal 可以达到单例的目的。这实际上是以空间换时间来实现线程间隔离的。

六、总结

单例模式可以保证内存里只有一个实例,减少了内存的开销;可避免对资源的浪费。

单例模式看起来非常简单,实现起来也不难,但是在面试中却是一个高频的面试题。希望大家能够彻底理解。


本篇文章所涉及的源代码:

github.com/eamonzzz

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

推荐阅读更多精彩内容