【设计模式】单例模式

参考书籍:《研磨设计模式》

一、为什么需要单例

        单例目的:保证一个类在内存中有且只有一个实例对象,并且作为外界访问的唯一接口。

        应用场景:很多项目的开发、应用都会有与之相关的配置文件,尤其是linux上的项目,大多需要配置端口、并发度、虚ip、选主模块信息等等,在开发的时候可能多个程序员会同时读取、修改同一份配置文件,这时候就有可能破坏数据的一致性从而导致开发进度受阻等情况发生,因此最好对这份文件进行互斥访问,规范、统一读取配置文件的入口,因此要求操作该配置文件的程序实例有且只有一个,并且所有人都是用该程序实例作为访问的入口。

二、单例模式的两种实现方式

        要保证一个类有且只有一个实例并且该实例作为全局唯一的方为接口,那么这个类就只能被实例化一次,如此可以设置一个static的计数器,初始值为零,实例化一次就加一,如果大于一实例化就返回失败,这样就可以保证这个类只有一个实例。如果项目的人数较少这样做勉强可以接受,但像windows这种几千人同时开发的项目,只要有一个人实例化了这个类,那么其他人不知道实例名称就无法访问配置文件;如果项目很小那也可以勉强接受,但如果项目中多个模块涉及次类型,那么光是交流、查看实例名称就已经不能高效开发了。

        设计模式:设计模式是一种可以在特定环境下解决重复出现的特定问题并且经过验证的解决方案。

        这就是我们为什么需要学习设计模式的原因,简而言之就是借用前人的经验,学为己用,兼而全之。要保证一个类只有一个实例且作为全局接口,一个成熟的解决方案就是单例模式,这个经过实际验证的模式有两种实现方式:懒汉式和饿汉式。无论是懒汉式还是饿汉式,首先都会私有构造方法,然后对外提供实力访问的统一接口。

  • 单例模式:饿汉式

package singleton;

/*
 * 单例模式:饿汉式
 */
public class SingletonHunger {
	/*
	 * static变量只在类加载的时候初始化一次,整个生命周期只初始化一次
	 * 因此将实例设置成static限制其初始化次数
	 */
	private static SingletonHunger hunger = new SingletonHunger();
	/*
	 * 私有化构造方法,这样就可以在内部控制创建实例的数目
	 */
	private SingletonHunger(){
		
	}
	/*
	 * 提供公共的方法作为唯一入口获取实例
	 * 饿汉式是线程安全的程序,因此不需要对获取实例的方法进行同步
	 */
	public SingletonHunger getInstance(){
		return hunger;
	}
}

  • 单例模式:懒汉式

package singleton;

/*
 * 单例模式:懒汉式
 */
public class SingletonLazier {
	/*
	 * 懒汉式采用了延迟加载的思想,等真正用到实例的时候再加载进内存
	 */
	private static SingletonLazier lazier;
	/*
	 * 私有化构造方法,这样可以在内部控制实例的数量
	 */
	private SingletonLazier(){
		
	}
	/*
	 * 提供一个公共方法作为实例访问的入口
	 * 因为实例还未初始化,因此该方法需要同步,否则有可能实例多次被初始化
	 */
	public synchronized SingletonLazier getInstance(){
		if(lazier != null)
			return lazier;
		return lazier = new SingletonLazier();
	}
	/*
	 * 提供更高效的同步方法获取实例,因为同步会消耗较多的资源
	 * 因此改进后的获取实例的方法只需要在第一次初始化实例的时候进行同步,之后无序进行同步
	 * 而为改进的方法每一次都需要同步
	 */
	public SingletonLazier getInstanceEffictive(){
		if(lazier == null){
			synchronized (SingletonLazier.class) {
				if(lazier == null)
					lazier = new SingletonLazier();
			}
		}
		return lazier;
	}
}

三、模式讲解

        无论是饿汉式还是懒汉式,都需要保证一个类在内存中有且只有一个实例,这个限制条件仅限于本次虚拟机(本次虚拟机烦请查看另一篇博文《【java】类加载机制》),即单例的作用范围是本次虚拟机。

  • 饿汉式

        1. 私有化构造方法:要想在运行期间控制一个类的实例数量,则要想办法控制该类创建实例的方法,因此最简单的方法就是私有化构造方法,这样就可以在内部控制实例的数量;

        2. 提供获取实例的方法:当一个类构造方法被私有,在外部就不能创建该类的实例,因此需要在类的内部提供获取实例的方法,懒汉式因为实例定义的时候被初始化,获取实力的方法不会导致实例变化, 因此该方法四线程安全,又因为该类构造方法被私有,因此要想访问获取实例的方法,该方法应该被static修饰;

        3. 静态内部实例:用静态修饰该类内部实例,一方面是因为获取方法是静态的,另一方面静态修饰的变量只会在类加载的时候初始化,并且整个生命周期内只初始化一次;

  • 懒汉式

        1. 私有化构造方法:与饿汉式相同;

        2. 提供获取实例的方法:因为懒汉式使用了延迟加载的思想,因此会在需要使用该类的时候才去加载实例,所以会在获取实例的方法中导致该类的实例被多次初始化,虽然依旧能保持该类在内存中的实例个数为一,但前一个实例和当前实例并不相等,因此需要同步该获取实例的方法;

        3. 静态内部实例:与懒汉式相同;

  • 两种模式比较

        饿汉式在定义内部实例的时候就对实例进行了初始化,该实例作为类变量会随着该类被加载进内而被分配内存空间,无论是否使用该实例都会占用内存;而懒汉式采用了延迟加载的思想,会在调用获取实例方法真的使用该实例的时候才会被加载分配内存,而不会随着类加载而分配内存,如果在程序运行期间并没有用到这个实力,那么就永远不会消耗该实例所占用的内存,但是懒汉式每次获取实力都会要消耗判断该实例是否初始化的时间。

        因此饿汉式是性能换取时间,而懒汉式是时间换取性能;

四、更好的单例实现方式:静态内部类

package singleton;

/*
 * 单例模式:静态内部类
 */
public class SingletonInnerClass {
	/*
	 * 私有化构造方法
	 */
	private SingletonInnerClass(){
		
	}
	/*
	 * 定义私有静态内部类
	 * 该内部类中的静态实例不会随着外部类的加载而分配内存
	 * static修饰成员虚拟机默认会执行同步,因此不用再显示处理实例的同步
	 */
	private static class SingletonInner {
		private static SingletonInnerClass inner = new SingletonInnerClass();
	}
	/*
	 * 提供获取实力的方法
	 */
	public static SingletonInnerClass getInstance(){
		return SingletonInner.inner;
	}
}

        1. 私有化构造方法,控制变量的实例数量;

        2. 定义静态内部类,再在内部类内部定义外部类的实例,一是静态虚拟机会默认执行同步,二是定义静态内部类不会随着外部类的加载而加载,只有调用获取示例的方法使用到内部类的时候才会被加载从而分配内存,至于为什么静态内部类不会随着外部类的加载而加载,这就好比定义一个静态变量而没有赋值一样,没有使用该内部类只是定义,就好像生命了类变量而没有分配值一样。

        3. 提供获取实例的方法,因为内部类及其实例被static修饰,虚拟机会隐式执行同步,所以该方法无需显示同步;

        所以,静态内部类实现单例模式,优势一时获取实例的方法无需同步和判断,高效;优势二是延迟加载,不占用更多的资源。


附注:

        本文如有错漏,烦请不吝指正,谢谢!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值