Clever Castle
1046 words
5 minutes
单例模式

In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. The term comes from the mathematical concept of a singleton

简介#

最基本的实例#

public class Emperor {
    private static final Emperor emperor=new Emperor();

    private Emperor(){}

    public static Emperor getInstance(){
        return emperor;
    }
}

Java Language Specification 中规定了一个类只会被初始化一次.所以该方法是线程安全的, 但是其在方法调用前就初始化了, 比较浪费资源.

优点#

  1. 只有一个实例, 节约内存空间, 减少了系统的性能开销, 如果某一个对象的产生需要比较多的资源时, 可以在启动时直接产生一个单例对象, 使其永驻内存.
  2. 可以避免对资源的多重占用,
  3. 可以在系统设置全局的访问点, 优化和共享资源访问.

缺点#

  1. 单例模式一般没有接口, 扩展很困难, 除了修改代码基本上没有第二种途径可以实现
  2. 单例模式不利于测试, 如果单例模式没有完成, 无法进行测试
  3. 与单一职责原则相冲突.

其它实现方式#

懒汉模式#

class Singleton {
    private static Singleton instance;

    private Singleton(){}

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

该实现只会在需要的时候才会进行初始化且是线程安全的, 但是每次调用getInstance()都会进行同步, 会浪费资源

Double Check Lock#

class SingletonDCL{
    private static SingletonDCL sInstance;

    private SingletonDCL(){}

    public static SingletonDCL getInstance(){
        if(sInstance==null){
            synchronized (SingletonDCL.class){
                if(sInstance==null){
                    sInstance=new SingletonDCL();
                }
            }
        }

        return sInstance;
    }
}

该实现只会在需要的时候才会进行初始化, 看似线程安全, 但实际并不是.

假设线程A执行到sInstance=new SingletonDCL(), 这句代码并不是一个原子操作, 这句代码大致会被分为下面3个步骤来处理:

  1. 给SingletonDCL的实例分配内存
  2. 调用SingletonDCL的构造函数, 初始化成员字段
  3. 将sInstance对象指向分配的内存空间 (此时sInstance就不是null了).

如果这句代码严格按照这个顺序执行,该DCL单例模式便是线程安全的, 但是事实并非如此. 原因是JVM并没有保证上述第2和第3步的执行顺序. 也就是说执行步骤可能是1-3-2, 这种执行步骤就会出问题:

当先执行第3步时, 另一个线程B开始执行getInstance(), 由于此时sInstance已经不是null了, 所以线程B会返回一个还未初始化的sInstance, 出现了错误.

JVM1.5之后改善了这个问题, 在sInstance前加上volatile关键字可以确保线程安全. 即private static volatile SingletonDCL sInstance;

静态内部类单例模式#

DCL单例模式并不推荐使用,《Java并发编程实践》推荐使用下面这个方法:

class SingletonStatic{
    private SingletonStatic(){}

    public static SingletonStatic getInstance(){
        return SingletonStaticHolder.sInstance;
    }

    private static class SingletonStaticHolder{
        private static final SingletonStatic sInstance=new SingletonStatic();
    }
}

第一次加载时, 并不会初始化sInstance, 只在第一调用getInstance()时初始化, 且该方法是线程安全的.

枚举单例#

enum SingletonEnum{
    INSTACNE;
    public void doSomething(){
        StdOut.println("doSomething...");
    }
}

枚举单例模式有以下3个优点:

  1. 线程安全, 任何时候都只有一个实例
  2. 反序列化时, 都只会有一个实例
  3. 可以防止反射攻击

选择哪一种实现方式#

无论采用哪一种实现方式, 都要确保线程安全, 防止反序列化导致重新生成实例对象等一些问题. 具体选择哪一种实现方式取决于项目本身.

关于序列化#

除了枚举单例, 为了避免单例对象在被反序列化时重新生成对象, 必须加入以下方法

private Object readResolve() throws ObjectStreamException{
    return sInstance;
}

单例模式扩展#

如果生成对象的数量不受限制, 可以直接使用new. 如果只要有一个对象, 使用单例模式即可, 若需要且只需要两个或者三个对象, 则可以按照下面的方法:

单例模式
https://blog.ivyxjc.com/posts/pattern-singleton/
Author
ivyxjc
Published at
2016-10-25