设计模式——单例模式

文章概要

1、什么是单例
2、为什么需要单例
3、单例的优点和缺点
4、单例的写法和比较
5、序列化破坏单例
6、反射破坏单例
7、不使用synchronized和lock,如何实现一个线程安全的单例?
8、JDK中的单例
9、引用

1、什么是单例

单例是23中设计模式之一,保证一个类只有一个实例,并且对外提供统一的访问入口。是创建型模式。

2、为什么需要单例

我们都知道,类的对象是由类的构造函数创建的,若构造函数是public的,则外部可以通过构造函数随意创建对象,若想限制类对象的创建,可以将构造函数改为private,至少也要protected。但是要保证类的可用性,就需要对外提供一个方法用于访问该类。

3、单例的优点和缺点

优点:在内存中一个类只有一个实例,避免对象的不断创建和销毁,减少内存开销
缺点:在编写单例代码时需要解决线程安全问题和序列化问题

4、单例的写法和比较

单例的写法分为饿汉式、懒汉式、静态内部类、枚举、

①饿汉式
public class Singleton {
    //实例化
    private static Singleton instance = new Singleton();
    
    //私有化的构造函数
    private Singleton(){}
    
    //对外提供一个统一获取实例的静态方法
    public static Singleton getInstance(){
        return instance;
    }
}

线程安全:安全。因为instance对象是static修饰的,在类第一次被加载时,instance就已经被初始化完成,所以线程安全。
缺点:类第一次被加载时就实例化,若实例用不到,会造成资源的浪费。若类被多次加载,会产生多个实例化对象。

②静态内部类
public class Singleton {

    //私有化的构造函数
    private Singleton(){}

    //静态内部类实例化
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    //对外提供统一方法
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

静态内部类的方式解决了饿汉式资源浪费的问题,当Singleton类第一次被加载时,instance不一定初始化,只有显示调用getInstance()方法时,才会装载SingletonHolder类,进而instance被初始化,这种延迟加载的方式解决了饿汉式浪费资源的问题。
线程安全:安全。与饿汉式一样用static修饰,利用classLoader机制的线程安全性保证此种单例的线程安全。

③懒汉式
public class Singleton {

    private static Singleton instance;

    //私有化的构造函数
    private Singleton(){}

    //对外提供统一方法
    public static Singleton getInstance(){
        if (null == instance){
            instance = new Singleton();
        }
        return instance;
    }
}

懒汉式是在对象第一次被使用时初始化instance。
线程安全:不安全。若线程A和线程B同时请求调用getInstance()方法,当A进入if判断,但是没有执行new操作时,B也进行if判断,此时inatance为null,这种情况线程A和线程B会分别new不同的Singleton对象。

④懒汉式改进版一
public class Singleton {

    private static Singleton instance;

    //私有化的构造函数
    private Singleton(){}

    //对外提供统一方法
    public static synchronized Singleton getInstance(){
        if (null == instance){
            instance = new Singleton();
        }
        return instance;
    }
}

为了解决懒汉式的线程安全问题,用synchronized修饰方法。
线程安全:安全。
缺点:由于getInstance方法被整个锁住,所以多线程环境下,此方法是串行执行的,执行效率低。

⑤懒汉式改进版二
public class DoubleCheckLazySingleton {
    private  static  DoubleCheckLazySingleton instance;

    private DoubleCheckLazySingleton(){};

    public static DoubleCheckLazySingleton getInstance(){
        if (instance == null){
            synchronized (DoubleCheckLazySingleton.class){
                if (instance == null){
                    instance = new DoubleCheckLazySingleton();
                }
            }
        }
        return instance;
    }
}

双重校验锁懒汉式单例,将synchronized锁范围缩小至初始化代码,从而提高执行效率,采用两次空值判断。
线程安全:不安全。
缺点:JVM可以执行重排序,指令的执行顺序:(1)分配内存给instance对象。(2)初始化instance对象。(3)设置instance对象指向分配内存地址。正常我们认为的执行顺序是(1)(2)(3),由于指令重排序机制,最后执行的顺序可能是(1)(3)(2),此时线程A执行(3),但没有执行(2),线程B进来之后判断instance对象非空(正在执行(2)或者未执行(2)),程序会产生错误。
那么如何解决上面的问题呢?volatile解决线程间的可见性和指令重排序问题。

public class DoubleCheckLazySingleton {
    private volatile static  DoubleCheckLazySingleton instance;

    private DoubleCheckLazySingleton(){};

    public static DoubleCheckLazySingleton getInstance(){
        if (instance == null){
            synchronized (DoubleCheckLazySingleton.class){
                if (instance == null){
                    instance = new DoubleCheckLazySingleton();
                }
            }
        }
        return instance;
    }
}

将instance变量用volatile修饰之后可解决以上问题。
但是它只是看上去完美无缺,其实还是有问题的。
缺点:序列化问题(后续展开说明)

⑥枚举单例
public enum EnumSingleton {
    INSTANCE;

    private EnumSingleton(){};

    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

这种方式是Effective Java作者Josh Bloch 提倡的方式。枚举类型单例,即解决了线程安全问题,有解决了序列化问题。
优点:

线程安全

枚举在底层做了线程安全的操作。

public final class EnumSingleton extends Enum
{

    public static EnumSingleton[] values()
    {
        return (EnumSingleton[])$VALUES.clone();
    }

    public static EnumSingleton valueOf(String name)
    {
        return (EnumSingleton)Enum.valueOf(com/xxx/EnumSingleton, name);
    }

    private EnumSingleton(String s, int i)
    {
        super(s, i);
    }

    public static EnumSingleton getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingleton INSTANCE;
    private static final EnumSingleton $VALUES[];

    static 
    {
        INSTANCE = new EnumSingleton("INSTANCE", 0);
        $VALUES = (new EnumSingleton[] {
            INSTANCE
        });
    }
}

以上代码是枚举类反编译之后的代码,我们发现变成了继承Enum类的final class类,变量instance用static修饰,instance的初始化在static块中,其实就是上面我们讲到的饿汉式单例。所以是线程安全的。

5、序列化破坏单例:

其他的单例模式都会有序列化问题,我们来以双重校验锁单例对象的序列化和反序列化做个测试:

public class SerializableTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testFile"));
        //写入双重校验锁单例对象
        DoubleCheckLazySingleton s1 = DoubleCheckLazySingleton.getInstance();
        oos.writeObject(s1);
        oos.flush();
        oos.close();

        //反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testFile"));
        DoubleCheckLazySingleton s2 = (DoubleCheckLazySingleton) ois.readObject();
        ois.close();

        System.out.println("s1:"+s1);
        System.out.println("s2:"+s2);
        System.out.println("s1==s2:"+ (s1==s2));

    }
}

输出结果:

s1:com.zhangjq.DoubleCheckLazySingleton@14ae5a5
s2:com.zhangjq.DoubleCheckLazySingleton@6d03e736
s1==s2:false

从结果可以看出序列化和反序列化不是同一个对象,那么是什么原因造成的呢?我们来看源码分析:
ObjectInputStream.readObject()

    public final Object readObject()
        throws IOException, ClassNotFoundException
    {
        if (enableOverride) {
            return readObjectOverride();
        }

        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(false);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
    }

在这里调用了readObject0(),在这个方法中可以看到

private Object readObject0(boolean unshared) throws IOException {
      ...
      case TC_OBJECT:
      return checkResolve(readOrdinaryObject(unshared));
      ...
}

我们继续进入readOrdinaryObject方法

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }

        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
        ·········
        return obj;
    }

可以看到这样一段代码obj = desc.isInstantiable() ? desc.newInstance() : null;
进入isInstantiable方法,发现就是一个判断是否存在无参构造函数的语句

/** serialization-appropriate constructor, or null if none */
    private Constructor<?> cons;
·········
/**
     * Returns true if represented class is serializable/externalizable and can
     * be instantiated by the serialization runtime--i.e., if it is
     * externalizable and defines a public no-arg constructor, or if it is
     * non-externalizable and its first non-serializable superclass defines an
     * accessible no-arg constructor.  Otherwise, returns false.
     */
boolean isInstantiable() {
        requireInitialized();
        return (cons != null);
    }

综合以上所述:我们发现只要序列化的类存在无参构造函数就调用desc.newInstance(),构造一个新的对象返回。

解决方案:

只需要在序列化的类中添加readResolve()方法即可解决序列化问题

public class DoubleCheckLazySingleton implements Serializable{
    private volatile static  DoubleCheckLazySingleton instance;

    private DoubleCheckLazySingleton(){};

    public static DoubleCheckLazySingleton getInstance(){
        if (instance == null){
            synchronized (DoubleCheckLazySingleton.class){
                if (instance == null){
                    instance = new DoubleCheckLazySingleton();
                }
            }
        }
        return instance;
    }

    private Object readResolve(){
        return instance;
    }
}

再次运行序列化和反序列化操作,结果如下:

s1:com.zhangjq.DoubleCheckLazySingleton@14ae5a5
s2:com.zhangjq.DoubleCheckLazySingleton@14ae5a5
s1==s2:true

为什么添加readResolve()方法就可以解决序列化问题呢?我们来看源码:
readOrdinaryObject()方法中,判断是否存在无参构造函数之后,又判断了是否存在readResolve()的方法

if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

hasReadResolveMethod:

/**
     * Returns true if represented class is serializable or externalizable and
     * defines a conformant readResolve method.  Otherwise, returns false.
     */
    boolean hasReadResolveMethod() {
        requireInitialized();
        return (readResolveMethod != null);
    }

若存在则执行hasReadResolveMethod方法,结果覆盖上面new 出来的对象,返回。
那么readResolveMethod 是在哪里赋值的呢?通过全局查找找到了赋值代码在私有方法
ObjectStreamClass()方法中给 readResolveMethod 进行赋值,来看代码:

readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);

在 invokeReadResolve()方法中用反射调用了 readResolveMethod 方法。
通过 JDK 源码分析我们可以看出,虽然,增加 readResolve()方法返回实例,解决了单
例被破坏的问题。但是,我们通过分析源码以及调试,我们可以看到实际上实例化了两
次,只不过新创建的对象没有被返回而已。那如果,创建对象的动作发生频率增大,就
意味着内存分配开销也就随之增大。

枚举单例序列化

对上述测试代码做修改

public class SerializableTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testFile"));
        //写入双重校验锁单例对象
//        DoubleCheckLazySingleton s1 = DoubleCheckLazySingleton.getInstance();
        EnumSingleton s1 = EnumSingleton.getInstance();
        oos.writeObject(s1);
        oos.flush();
        oos.close();

        //反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testFile"));
//        DoubleCheckLazySingleton s2 = (DoubleCheckLazySingleton) ois.readObject();
        EnumSingleton s2 = (EnumSingleton) ois.readObject();
        ois.close();

        System.out.println("s1:"+s1);
        System.out.println("s2:"+s2);
        System.out.println("s1==s2:"+ (s1==s2));

    }
}

运行结果:

s1:INSTANCE
s2:INSTANCE
s1==s2:true

没什么任何附加的操作,就可以保证序列化问题,我们来看源码分析:
继续分析ObjectInputStream.readObject(),在readObject0方法中

                case TC_ENUM:
                    return checkResolve(readEnum(unshared));

我们看到在 readObject0()中调用了 readEnum()方法,来看 readEnum()中代码实现:

    private Enum<?> readEnum(boolean unshared) throws IOException {
        if (bin.readByte() != TC_ENUM) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        if (!desc.isEnum()) {
            throw new InvalidClassException("non-enum class: " + desc);
        }

        int enumHandle = handles.assign(unshared ? unsharedMarker : null);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(enumHandle, resolveEx);
        }

        String name = readString(false);
        Enum<?> result = null;
        Class<?> cl = desc.forClass();
        if (cl != null) {
            try {
                @SuppressWarnings("unchecked")
                Enum<?> en = Enum.valueOf((Class)cl, name);
                result = en;
            } catch (IllegalArgumentException ex) {
                throw (IOException) new InvalidObjectException(
                    "enum constant " + name + " does not exist in " +
                    cl).initCause(ex);
            }
            if (!unshared) {
                handles.setObject(enumHandle, result);
            }
        }

        handles.finish(enumHandle);
        passHandle = enumHandle;
        return result;
    }

可以看出枚举对象反序列化时是通过Enum.valueOf()方法根据名字查找枚举对象 。不存在new新对象的情况,所以枚举单例的反序列化问题是安全的。

6、反射破坏单例

在上述所有的单例模式中,构造方法都是private权限的,很多人以为只要设置成private就是安全的了,其实不然,利用反射机制可以强制访问private的构造方法来创建对象。我们来看一个例子:

public class ReflectSingletonTest {
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        //以双重校验锁单例为例
        Class<?> clazz = DoubleCheckLazySingleton.class;
        Constructor<?> declaredConstructor = clazz.getDeclaredConstructor();

        //设置强制访问
        declaredConstructor.setAccessible(true);

        //初始化
        Object o1 = declaredConstructor.newInstance();
        Object o2 = declaredConstructor.newInstance();

        System.out.println("o1:"+o1);
        System.out.println("o2:"+o2);
        System.out.println("o1==o2:"+ (o1==o2));
    }
}

运行结果:

  o1:com.zhangjq.DoubleCheckLazySingleton@1b6d3586
o2:com.zhangjq.DoubleCheckLazySingleton@4554617c
o1==o2:false
解决方案:

针对这种情况,我们可以在构造函数里面加一些判断,来解决反射破坏单例的问题:

public class DoubleCheckLazySingleton implements Serializable{
    private volatile static  DoubleCheckLazySingleton instance;
    private volatile static boolean initialized = false;
    private DoubleCheckLazySingleton(){
        if(!initialized){
            initialized = !initialized;
        }else{
           throw new RuntimeException("不允许重复创建DoubleCheckLazySingleton对象!");
        }  
    }

    public static DoubleCheckLazySingleton getInstance(){
        if (instance == null){
            synchronized (DoubleCheckLazySingleton.class){
                if (instance == null){
                    instance = new DoubleCheckLazySingleton();
                }
            }
        }
        return instance;
    }

    private Object readResolve(){
        return instance;
    }
}

再次运行结果如下:

java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.zhangjq.ReflectSingletonTest.main(ReflectSingletonTest.java:25)
Caused by: java.lang.RuntimeException: 不允许创建多个实例
    at com.zhangjq.LazyDoubleCheckSingleton.<init>(LazyDoubleCheckSingleton.java:13)
    ... 5 more
枚举单例反射问题

那么枚举类型的单例是否存在反射问题呢?

public class ReflectSingletonTest {
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        //以双重校验锁单例为例
        Class<?> clazz = EnumSingleton.class;
        Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(null);

        //设置强制访问
        declaredConstructor.setAccessible(true);

        //初始化
        Object o1 = declaredConstructor.newInstance();
        Object o2 = declaredConstructor.newInstance();

        System.out.println("o1:"+o1);
        System.out.println("o2:"+o2);
        System.out.println("o1==o2:"+ (o1==o2));
  }
}

输出结果:

Exception in thread "main" java.lang.NoSuchMethodException: com.zhangjq.EnumSingleton.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    at com.zhangjq.ReflectSingletonTest.main(ReflectSingletonTest.java:13)

异常的意思是没有找到EnumSingleton无参的构造函数。
查看Enum类的源码发现只有一个带参数的构造函数

protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

我们将代码修改一下,再次运行:

public class ReflectSingletonTest {
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        //以双重校验锁单例为例
        Class<?> clazz = EnumSingleton.class;
        Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(String.class,int.class);

        //设置强制访问
        declaredConstructor.setAccessible(true);

        //初始化
        Object o1 = declaredConstructor.newInstance("zhang",10);
        Object o2 = declaredConstructor.newInstance("wang",11);

        System.out.println("o1:"+o1);
        System.out.println("o2:"+o2);
        System.out.println("o1==o2:"+ (o1==o2));
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    at com.zhangjq.ReflectSingletonTest.main(ReflectSingletonTest.java:19)

报错信息很明显:不能用反射创建枚举类对象。
我们查看newInstance()方法的源码

    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

可以看出当修饰符是枚举类型时,抛出异常,也就是说jdk控制反射不能创建枚举类对象。所以枚举类的反射破坏问题是不存在的。

7、不使用synchronized和lock,如何实现一个线程安全的单例?

以上我们讲的所有单例的线程安全都显示或者隐示的用到了ClassLoader的线程安全机制。
饿汉式:用static修饰,类第一次被加载时实例化,ClassLoader的类加载是synchronized修饰的。
懒汉式:显示的用synchronized修改。
枚举:编译器编译之后枚举中的所有变量都用static final 修饰,并且是初始化在static块中
那么如果我们不用synchronized和lock,如何实现一个线程安全的单例呢?
答案是:CAS

CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
实现单例代码:

public class Singleton {
    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>(); 

    private Singleton() {}

    public static Singleton getInstance() {
        for (;;) {
            Singleton singleton = INSTANCE.get();
            if (null != singleton) {
                return singleton;
            }

            singleton = new Singleton();
            if (INSTANCE.compareAndSet(null, singleton)) {
                return singleton;
            }
        }
    }
}

优点:CAS依赖底层硬件的实现,没有线程之间切换和阻塞问题。
缺点:可能一直处于等待中,不断的循环,对CPU造成资源开销。

8、JDK中的单例

java.lang.Runtime

Runtime类封装了Java运行时的环境。每一个java程序实际上都是启动了一个JVM进程,那么每个JVM进程都是对应这一个Runtime实例,此实例是由JVM为其实例化的。每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。
由于Java是单进程的,所以,在一个JVM中,Runtime的实例应该只有一个。所以应该使用单例来实现。

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {}
}

以上代码为JDK中Runtime类的部分实现,可以看到,这其实是饿汉式单例模式。在该类第一次被classloader加载的时候,这个实例就被创建出来了。

一般不能实例化一个Runtime对象,应用程序也不能创建自己的 Runtime 类实例,但可以通过 getRuntime 方法获取当前Runtime运行时对象的引用。

9、引用

参考:
单例与序列化的那些事儿
设计模式(二)——单例模式
不使用synchronized和lock,如何实现一个线程安全的单例?
JDK中的那些单例

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

推荐阅读更多精彩内容

  • 微信原文:设计模式 | 单例模式及典型应用 单例是最常见的设计模式之一,实现的方式非常多,同时需要注意的问题也非常...
    小旋锋的简书阅读 1,767评论 2 5
  • 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。 单例模式的使用很广泛,比如:线程池(threa...
    richy_阅读 290评论 0 0
  • 在项目开发时有一些对象其实我们只需要一个,比如:线程池、缓存、日志对象等等。这类对象只能有一个实例,如果制造出多个...
    41uLove阅读 438评论 0 0
  • 目录 本文的结构如下: 什么是单例模式 为什么要用该模式 模式的结构 代码示例 优点和缺点 适用环境 模式应用 总...
    w1992wishes阅读 385评论 1 2
  • 单例模式(Singleton Pattern)是众多设计模式中较为简单的一个,同时它也是面试时经常被提及的问题,如...
    廖少少阅读 553评论 0 1