• 设计模式之单例模式


    在实际的项目开发中,常会用到配置文件,可在读取配置文件后将读取的内容放在数据对象中,但在使用时通过new的方式产生对象,在系统中会存在多个相同的配置文件,当配置文件过多时会极大的影响到系统的性能。我们是否能使得配置文件的实例对象在系统运行期间只有一个,以避免了上述问题的发生,这就是我们今天说的单例模式。

    一、单例模式实现

    单例模式也称单件、单体等,单例模式保证一个类只有一个实例,并提供一个全局访问点。一个类能够创建多个实例的根源是类的构造方法是公开的,因此,要控制类的创建,首先就要收回创建类实例的权限,同时提供可供外部访问类实例的方法。

    单例模式的结构图如下所示:

    单例模式的实现方式分为两种:懒汉式和饿汉式,他们主要是在创建实例对象的处理方式不同。下面分别对两种方式进行实现:

    • 饿汉式单例
    public class Student {
        // 成员变量初始化本身对象
    	private static Student student = new Student();
        
        // 1:构造私有,内部控制实例数
    	private Student() {
    	}
    
    	// 2:对外提供公共方法获取对象
    	public static Student getInstance() {
    		return student;
    	}
    }
    
    • 懒汉式单例
    public class Student {
    
    	private Student() {
    	}
    	
    	//此处使用一个内部类来维护单例 JVM在类加载的时候,是互斥的,所以可以由此保证线程安全问题
    	private static class SingletonFactory {
    		private static Student student = new Student();
    	}
    	/* 获取实例 */
    	public static Student getInstance() {
    		return SingletonFactory.student;
    	}
    }
    

    懒汉与饿汉是一种形象的称谓,饿汉是在装载类时创建对象实例,而懒汉式在使用 的时候才进行对象的创建。

    二、单例模式说明

    单例模式的本质是控制实例数目。单例模式保证类运行期间只有一个实例对象,并提供一个全局访问点,即示例代码中的getInstance方法。Java中实现的单例的范围是一个虚拟机,虚拟机通过自己的ClassLoader装载饿汉式实现单例类创建类实例。

    单例模式调用的示意图如下所示:

    懒汉式调用顺序:

    饿汉式调用顺序:

    单例模式的懒汉加载方式体现了延迟加载的思想。只有当使用资源或数据时才进行加载,称为“lazy load”,起到尽可能节约资源的效果。

    此外,懒汉式加载模式还体现了缓存的思想,即当数据或资源被频繁访问时,将数据魂村到内存中,每次先到内存中查找数据,有则使用,无则获取,从而节约时间,这也是一种典型的空间换时间的方案

    1、单例模式的线程安全

    饿汉式是线程安全的,而不加同步的懒汉式加载是线程不安全的,当A,B两个线程同时进入getInstance方法,当A进行实例是否存在判断时,B线程已经开始进行实例的创建,程序运行,A线程也会进行实例的创建,会创建出两个实例,这里的单例在并发情况下失效。

    下面为不加同步的懒汉式加载代码:

    public class Student {
    	private Student() {
    	}
    
    	private static Student student = null;
    	
    	public static Student getInstance() {
            if(student == null){
                student = new Student();
            }
    		return student;
    	}
    }
    

    而懒汉式的也可以实现线程安全,可通过给getInstance方法添加synchronized关键字实现,但会降低访问速度。

    此外,也可以通过双重检查加锁的方式实现的,不仅能够保证线程安全,也不会对性能产生大的影响。双重检查锁机制是指进入方法后先检查实例是否存在,这是第一重判断,若实例不存在进入同步代码块。在同步代码块中检查实例是否存在,若不存在则创建实例,这是第二重判断。

    双重检查锁机制使用关键字volatile,被其修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程正确处理该变量。示例代码如下:

    public class Student {
    	private Student() {
    	}
    	//实例变量volatile修饰
    	private volatile static Student student = null;
    	
    	public static Student getInstance() {
            //检查实例是否存在
            if(student == null){
                //同步块,线程安全创建实例
                synchronized(Student.class){
                    //检查实例是否存在,不存在则创建
                    if(student == null){
                        student = new Student();
                    }
                }
            }
    		return student;
    	}
    }
    

    注意:

    volatile关键字可能屏蔽掉虚拟机中必要的优化代码,且效率不高,因而没有特别需求,一般不使用。

    2、Java单例模式实现

    上面的单例模式实现存在缺陷,在Java中有一种Lazy initlialization holder calss模式,使用Java内部类和多线程缺省同步锁实现类延迟加载与线程安全。

    类级内部类指有static修饰的成员式内部类,无static修饰的称为对象级内部类,他的特点如下:

    • 类级内部类与外部类中的static修饰的相似,与外部类对象无依赖关系。而对象级内部类与外部类实例对象绑定。
    • 类级内部类中可定义静态方法,静态方法中只能引用外部类中的静态成员方法或成员变量。
    • 类级内部类相当于外部类的成员,只在第一次使用时被装载。

    Java开发中主要通过synchronized加互斥锁进行同步控制,但在某些情况中,JVM已经隐含的执行了同步,其中就包括由静态初始化器初始化数据时,可采用这种方式由JVM保证线程的安全性。其实力代码如下:

    public class Student {
    
    	private Student() {
    	}
    	
    	//类的内部类,即静态成员内部类,它只有被调用的时在会装载,从而实现延迟加载
    	private static class SingletonFactory {
            //静态初始化器,由JVM保证线程安全
    		private static Student student = new Student();
    	}
    	/* 获取实例 */
    	public static Student getInstance() {
    		return SingletonFactory.student;
    	}
    }
    

    即我们在模式实现时给出的示例代码。

    3、枚举单例模式实现

    单元素的枚举类型也可以进行单例模式的实现,枚举有一些特点:

    • Java的枚举类型是功能齐全的类型,可以有自己的属性和方法。
    • Java的枚举类型基本思想是通过公有的静态final域为枚举常量导出实例。
    • 枚举是单例的泛型化,单例的本质是单元素枚举。

    枚举实现单例的代码如下:

    public enum Singleton{
        //枚举元素
        uniqueInstance;
        
        public void singletonOperation(){
            //功能实现
        }
    }
    

    三、单例模式总结

    1、适用场景

    需要控制类的实例只能有一个,且客户端能从全局访问点访问实例。

    2、模式优缺点

    • 时间与空间。懒汉式是典型的时间换空间,饿汉式是典型的空间换时间。懒汉式浪费运行时间进行是否创建的判断,饿汉式是先创建,调用不在进行判断,节约运行时间。

    • 线程安全。饿汉式是线程安全的,而不加同步的懒汉式加载是线程不安全的,但可通过关键字synchronized或volatile将懒汉式加载变为线程安全的。此外,Java实现了线程安全与延迟加载兼顾的单例模式实现方式。

    参考:文章主要参考《研磨设计模式》一书

  • 相关阅读:
    基于ExtAspNet的开源项目 Ext4JSLint
    ExtAspNet应用技巧(九) Grid导出为Excel文件
    ExtAspNet应用技巧(十二) 系统登录
    ExtAspNet v2.0.7
    原来最可怕的不是工作,是无聊
    获取Word文档的作者和主题
    《可变范围规约》
    用IronPython加载,写入文本文件
    IronPython中没有System.Data命名空间?
    《敏捷建模》读后感
  • 原文地址:https://www.cnblogs.com/liuyi6/p/10344285.html
Copyright © 2020-2023  润新知