设计模式(一)——单例模式

224 阅读4分钟

单例模式

​ 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。构造器私有化,不能被new出来。

项目代码:Github

单例的应用

优点

  1. 在内存中只有一个实例, 减少了内存开支
  2. 当一个对象的产生需要 比较多的资源时, 如读取配置、 产生其他依赖对象时, 则可以通过在应用启动时直接产生一 个单例对象, 然后用永久驻留内存的方式来解决
  3. 单例模式可以避免对资源的多重占用, 例如一个写文件动作, 由于只有一个实例存在 内存中, 避免对同一个资源文件的同时写操作。
  4. 单例模式可以避免对资源的多重占用, 例如一个写文件动作, 由于只有一个实例存在 内存中, 避免对同一个资源文件的同时写操作。

缺点

  1. 单例模式一般没有接口, 扩展很困难;
  2. 单例模式对测试是不利的。 在并行开发环境中, 如果单例模式没有完成, 是不能进行 测试的, 没有接口也不能使用mock的方式虚拟一个对象。
  3. 单例模式与单一职责原则有冲突。

几种单例实现:

1、饿汉模式

public class SingleDemo {
    private static SingleDemo instance = new SingleDemo();
    //私有化构造器
    private SingleDemo() {
        //防止其他通过反射调用构造方法,破解单例
        if (instance != null) {
            throw new RuntimeException();
        }
    }
    //对外提供统一的访问点
    public static SingleDemo getInstance() {
        return instance;
    }
}

优点:

  • 实例的初始化由JVM装载类的时候进行,保证了线程的安全性
  • 实现简单方便,访问效率高

缺点:

  • 不能实现懒加载,资源的利用率不高

2、懒汉模式

public class SingleDemo2 {
    // 此处并不初始化实例
    private static SingleDemo2 instance;

    private SingleDemo2() {
        if (instance != null) {
            throw new RuntimeException();
        }
    }
    /**
     * 当调用此方法的时候才初始化实例, 为了实现线程安全,需要使用同步方法
     */
    public static synchronized SingleDemo2 getInstance() {
        if (instance == null) {
            instance = new SingleDemo2();
        }
        return instance;
    }
}

优点:

  • 只有使用这个类的时候才初始化实例,优化了资源利用率

缺点:

  • 为了实现线程安全,使用了同步方法获取,增加了访问的开销

3、双重检查

public class SingleDemo3 {
    private static SingleDemo3 instance;

    private SingleDemo3() {
        if (instance != null) {
            throw new RuntimeException();
        }
    }

    public static SingleDemo3 getInstance() {
        //第一重检查,提高效率
        if (instance == null) {
            synchronized (SingleDemo3.class) {
                //第二重检查保证线程安全
                if (instance == null) {
                    instance = new SingleDemo3();
                }
            }
        }
        return instance;
    }
}

优点:

  • 实现懒加载
  • 通过缩小同步区域和第一次检查提高访问效率

缺点:

  • 为了实现线程安全,使用了同步方法获取,增加了访问的开销

4、静态内部类

public class SingleDemo4 {
    private static SingleDemo4 instance;

    private static class SingleDemo4Holder {
        private static final SingleDemo4 instance = new SingleDemo4();
    }

    private SingleDemo4() {
        if (instance != null) {
            throw new RuntimeException();
        }
    }

    /**
     * 调用这个方法的时候,JVM才加载静态内部类,才初始化静态内部类的类变量。由于由JVM初始化,保证了线程安全性,
     * 同时又实现了懒加载
     */
    public static SingleDemo4 getInstance() {
        return SingleDemo4Holder.instance;
    }
}

优点:

  • 即实现了线程安全,又实现了懒加载

缺点:

  • 实现稍显复杂

5、枚举类

public enum SingleDemo5 {
     INSTANCE;
    public void someMethod(){
        
    }
}

优点:

  • 实现简单
  • 线程安全

缺点:

  • 不能实现懒加载

结论

如果需要懒加载就使用静态内部类方式,如果不需要就使用枚举方式

单例模式的扩展

如果要求一个类只能生产固定数量的实例。

public class SingleDemo6{
    // 最多可以生成的单例数量
    private static int maxNumberSingleDemo = 2;
    // 定义列表存放实例
    private static List<SingleDemo6> singleDemoList = new ArrayList<>();
    
    //生成对象
    static{
        for(int i=0; i<maxNumberSingleDemo; i++){
            singleDemoList.add(new SingleDemo6());
        }
    }
    
    private SingleDemo6(){}
    
    public static SingleDemo6 getInstance(){
        Random random = new Random();
        //随机调用一个实例
        int number = random.nextInt(maxNumberSingleDemo);
        return singleDemoList.get(number);
    }
    
}

这种需要产生固定数量对象的模式就叫做有上限的多例模式, 它是单例模式的一种扩展, 采用有上限的多例模式, 我们可以在设计时决定在内存中有多少个实例, 方便系统进行 扩展, 修正单例可能存在的性能问题, 提供系统的响应速度。 例如读取文件, 我们可以在系 统启动时完成初始化工作, 在内存中启动固定数量的reader实例, 然后在需要读取文件时就 可以快速响应。


欢迎关注公众号:

公众号微信