单例模式

单例模式(Singleton Pattern)的定义:

确保某个类只有一个实例,而且自行实例化并向整个系统提供整个实例。

通用类图如下:

单例模式通用类图

Singleton类成为单例类,通过使用private的构造函数确保在应用中只产生一个实例,并且是自行实例化的。其通用代码如下:

package 单例模式;

/**
 * 单例模式的通用代码
 */
public class Singleton {
    private static final Singleton singletom = new Singleton();

    //限制产生多个对象
    private Singleton() {

    }

    //通过该方法获取实例对象
    public static Singleton getSingletom() {
        return singletom;
    }

    //类中的其他方法,尽量是static
    public static void doSomething() {

    }
}

单例模式的应用

单例模式的优点
  • 由于单例模式在内存中只有一个实例,减少内存开支。
  • 由于单例模式只生成一个实例,所以减少了系统的性能开销。
  • 可以避免对资源的多重占用。
  • 可以在系统设置全局访问点。
单例模式的缺点
  • 单例模式一般没有借口,扩展困难。若要扩展,除了修改代码外基本上没有第二种途径。
  • 单例模式对测试不利。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
  • 与单一职责原则有冲突,单例模式把“要单例”和业务逻辑融合在一个类中。
单例模式使用场景
  • 要求生产唯一序列号的环境。
  • 在项目中需要一个共享访问点或共享数据,比如Web页面上的计数器。
  • 创建对象需要消耗的资源过多,如I/O和数据库等资源。
  • 需要定义大量的静态资源和静态方法(如工具类)的环境(当然,也可以直接声明为static的方式)。
单例模式的注意事项

在高并发情况下,注意单例的同步问题

上面的例子不会产生多个实例的情况,但是如下实例为线程不安全的单例:

public class Singleton {
    private static Singleton singletom = null;

    //限制产生多个对象
    private Singleton() {}

    //通过该方法获取实例对象
    public static Singleton getSingletom() {
        if(singleton == null)
            singleton = new Singleton();
        return singleton;
    }
}

该单例在低并发的情况下不会出现问题,若系统压力过大,并发量增大时则可能在内存中出现多个实例。如一个线程A执行到singleton = new Singleton(),但还没有获得对象(对象初始化是要时间的),而此时第二个线程B也在执行,并判断singleton !=null,那么线程B将继续运行下去而又创建一个新的对象。

可以使用Synchronized关键字来解决这个问题,但是在访问频繁的情况下性能开销很大,建议使用最上面给出的方式。

考虑对象的复制问题

在Java中对象默认不可复制,需要实现Cloneable接口并实现clone方法。对象复制是不需要调用类的构造函数,因此即使构造函数私有依然可以被复制。一般情况下,很少有单例会主动要求实现Cloneable接口。


单例模式实例

皇帝只有一个,大臣参拜皇帝,用单例来实现此模型:

皇帝类:

public class Emperor {
    private static final Emperor emperor = new Emperor();//初始化一个皇帝

    //构造方法私有,不允许有多个皇帝
    private Emperor() {

    }

    //获取单一实例
    public static Emperor getInstance() {
        return emperor;
    }

    //动作
    public static void say() {
        System.out.println("我是皇帝");
    }
}

臣子类:

public class Minister {
    public static void main(String[] args) {
        for (int day = 0; day < 3; day++) {
            Emperor emperor = Emperor.getInstance();
            emperor.say();
        }
    }
}

臣子每天都参拜同一个皇帝。


单例模式的扩展

假设皇帝有两位,需要指定固定数量实例的单例模式。

固定数量的皇帝类:

import java.util.ArrayList;
import java.util.Random;

/**
 * 固定数量的皇帝类
 */
public class Emperor2 {
    //定义最多能产生的实例数量
    private static int maxNumOfEmperor = 2;
    //每个皇帝都有名字,使用一个ArrayList来容纳,每个对象的私有属性
    private static ArrayList<String> nameList = new ArrayList<>();
    //定义一个列表,容纳所有的皇帝实例
    private static ArrayList<Emperor2> emperor2List = new ArrayList<>();
    //当前皇帝的序号
    private static int countNumOfEmperor = 0;

    //产生所有的对象
    static {
        for (int i = 0; i < maxNumOfEmperor; i++) {
            emperor2List.add(new Emperor2("黄" + (i + 1) + "帝"));
        }
    }

    private Emperor2() {
        //不能产生两个皇帝
    }

    //传入皇帝名称,建立一个皇帝对象
    private Emperor2(String name) {
        nameList.add(name);
    }

    //随机获得一个皇帝对象
    public static Emperor2 getInstance() {
        Random random = new Random();
        //随机拉出一个皇帝,只要是个精神领袖就成
        countNumOfEmperor = random.nextInt(maxNumOfEmperor);
        return emperor2List.get(countNumOfEmperor);
    }

    //皇帝发话了
    public static void say() {
        System.out.println(nameList.get(countNumOfEmperor));
    }
}

臣子参拜皇帝的过程:

public class Minister2 {
    public static void main(String[] args) {
        //定义五个大臣
        int ministerNum = 5;
        for (int i = 0; i < ministerNum; i++) {
            Emperor2 emperor = Emperor2.getInstance();
            System.out.println("第" + (i + 1) + "个大臣参拜的是:");
            emperor.say();
        }
    }
}

这种需要产生固定数量的单例模式叫多例模式,它是单例的一种扩展。我们可以在设计时决定内存中有多少个实例,方便系统进行扩展,修正单例可能存在的性能问题,通过系统的响应速度。例如读取文件,我们可以在系统启动时创建多个reader实例,然后需要在读取文件的时候可以快速响应。


参考文献:《设计模式之禅》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值