单例模式

   单例模式是一种常见的设计模式,其核心为“每个类只能有一个实例”。通过对类中对象实例化的控制,确保某些场景下状态的唯一性。在系统中的某些类只有一个实例非常重要,如打印任务、一个窗口管理器或文件系统等。

  背景

       在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。例如:系统中可以存在多个打印任务,但只能用一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器;一台计算机可以有若干传真卡,但是只应该有一个软件负责管理传真卡,以避免出现两份传真作业同时传到传真卡中的情况;一台计算机可以有若干通信端口,但是系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用等。因此,单例模式可以被广泛的应用的下述环境中:

  • 要求生成唯一序列号的环境;
  • 整个项目中需要一个共享访问点或共享数据。如,一个web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,确保线程安全;
  • 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;
  • 需要定义大量的静态常量和静态方法(如工具类)的环境,采用单例模式(或static声明);

一、定义

      单例模式(Singleton Pattern)即某各类中只有一个实例,且自行实例化并向整个系统提供这个实例。

二、特点

  • 单例类只能有一个实例。
  • 单例类必须自己自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

三、实现

举例:一个国家只能有一个皇帝(即单例类);

3.1 单例模式通用

例1:
/* 单例模式通用代码*/

package simplePattern;

public class Singleton {
    //定义各一个单例类
    private static final Singleton singleton = new Singleton();
    //定义私有的构造函数,限制一个类只能产生一个对象
    private Singleton(){
        
    }
    public static Singleton getSingleton(){
        return singleton;
    }
    //类中其他方法能够其他类调用
    public static void doSomething(){
        
    }
}

然而,单例模式也存在一些缺点。如:

  • 一般没有接口,扩展困难;
  • 不利于测试。在并发环境中,如果单例模式没有完成,不能进行测试,没有接口也无法使用mock的方式虚拟一个对象。
  • 单例模式与单一职责原则有冲突。单例模式中把“要单例”和业务逻辑融合在一个类中

因此,针对具体情况可以对单例模式进行改进。

3.2 高并发下单例模式实现

例2:
public class Singleton_hignconcur {
    private static Singleton_hignconcur singleton= null;
    //定义私有的构造函数,限制一个类只能产生一个对象
    private Singleton_hignconcur(){
        
    }
    //方法
    public static Singleton_hignconcur getSingleton(){
        if(singleton==null){
            singleton=new Singleton_hignconcur();
        }
        return  singleton;
    }
}

  然而,上述代码实现可能存在线程A执行到singleton = new Singleton(),线程B则依然执行到(singleton == null)判断时依然为真,此时,由于A尚未返回实例判断条件仍未null,此时可能出现2个对象,因此线程不安全。

  解决上述线程不安全的方法一般有2种,(1)在getSingleton()方法前添加synchronized关键字,或在该方法内添加synchronized关键字,也可使用饿汉式单例(例1)或懒汉式单例模式(例2);(2)考虑对象复制的情况。在JAVA中对象默认不能复制,若实现了Cloneable接口,并实现clone方法,则可以直接通过对象复制方式创建一个新对象,对象复制不需要调用类的构造函数,因此,即使是私有的构造函数,对象仍可被复制。因此,尽量使单例类不实现Cloneable接口。

4. 单例模式扩展

   一个类需要多个对象时,直接使用new关键字即可,只能有一个对象时,使用单例模式,但对于要求某各类只能有n(n可为任一固定自然数)个对象时,如何实现?

public class Emperor {
    //定义最多能产生的对象数量
        private static int maxNum=3;
        //每个对象(皇帝)拥有的属性(姓名),使用list存储
        private static ArrayList<String> nameList = new ArrayList<String>();
        //定义一个列表,容纳所有的对象实例
        private static ArrayList<Emperor> emperorList= new ArrayList<Emperor>();
        //定义当前皇帝的序号
        private static int order=0;
        //产生所有对象
        static {
            for(int i=0;i<maxNum;i++){
                emperorList.add(new Emperor("皇帝"+(i+1)));
            }
        }
        private Emperor(){
            //存在约束条件(世俗和道德),使得不产生第二个皇帝
        }
        //传入皇帝名称,建立一个对象
        private Emperor(String name){
            nameList.add(name);
        }
        //随机获得一个皇帝实例
        public static Emperor getInstance(){
            Random ran=new Random();
            order= ran.nextInt(maxNum);
            return emperorList.get(order);
        }
        //方法
        public static void say(){
            System.out.println(nameList.get(order));
        }
}
public class Minister {
    public static void main(String[] args){
        //定义5个大臣
        int maxNum=5;
        for(int i=0;i<=maxNum;i++){
            Emperor emp=Emperor.getInstance();
            System.out.println("第"+(i+1)+"个臣子的是:");
            emp.say();
        }
    }
}

运行结果如下:

  这种需要产生固定数量对象的模式称作有上限的多例模式,是单例模式的一种扩展。采用多例模式可以在设计时觉得在内存中有多少个实例,方便系统进行扩展,修正可能存在的性能问题,提供系统响应速度。

参考《大话设计模式》与《设计模式之禅》

原文地址:https://www.cnblogs.com/mo-lu/p/10274144.html