JAVA设计模式之单例模式

其实网上已经有很多介绍单例模式甚至是所有23种设计模式的了,都讲得很好,所以这里我不敢也不想说是为大家解疑惑,只是作为自己学习过程的笔记,以便以后进行查阅。

 

单例模式的概念就不在具体介绍,其核心本质就是一种对象创建的模式,用于产生一个对象的具体实例,它可以确保一个类对象只有一个实例。这样做的好处是:

(1)对于频繁使用的对象,可以省略每次创建对象所花费的时间,这对于一些重量级对象而言,是一笔非常可观的系统开销。——时间方面

(2)由于new操作次数减少,因而对系统内存使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。——内存方面

因此对于系统的关键组件和被频繁使用的对象,使用单例模式可以有效改善系统的性能。

 

单例模式只有两个参与者,分别是:

(1)单例类:提供单例的工厂,返回单例;

(2)使用类:获取并使用单例类。

 

单例模式的结构图:

 

 

下面由浅入深地介绍几种实现单例模式的方式,我们这时应该在心里默念:单例模式的核心在于通过一个接口返回唯一的对象实例。

一、最简单的实现方式

public class Singleton{

	<span style="color:#339999;">/*
	 * 单例类必须要有一个private访问级别的构造函数,
	 * 这样保证了单例不会在系统中的其他代码内被实例化
	 */
</span>	<strong>private</strong> Singleton(){	
		<span style="color:#339999;">//创建单例模式的过程可能缓慢
</span>		System.out.println("Singleton is created!");
	}
	
	<span style="color:#339999;">/*
	 * instance成员变量必须是static
	 */
</span>	<strong>private static</strong> Singleton instance = new Singleton();
	
	<span style="color:#339999;">/*
	 * getInstance函数必须是static
	 */
</span>	<strong>public static</strong> Singleton <strong>getInstance</strong>(){
		return instance;
	}
}

优点:实现方式简单,而且可靠。

缺点:唯一的不足是无法对instance实例做延迟加载。

 

假如单例创建过程缓慢,由于instance成员变量是static定义的,因此在JVM加载单例类时,单例对象就会被建立,如果此时,这个单例类在系统中还扮演其他角色,那么在任何使用这个单例类的地方都会初始化这个单例变量,而不管是否会被用到。比如单例类作为String工厂,用于创建一些字符串(该类既用于创建单例Singleton,也用于创建String对象)。

public class Singleton{

	<span style="color:#339999;">/*
	 * 单例类必须要有一个private访问级别的构造函数,
	 * 这样保证了单例不会在系统中的其他代码内被实例化
	 */
</span>	private Singleton(){	
		<span style="color:#339999;">//创建单例模式的过程可能缓慢
</span>		System.out.println("Singleton is created!");
	}
	
	<span style="color:#339999;">/*
	 * instance成员变量必须是static
	 */
</span>	private static Singleton instance = new Singleton();
	
	<span style="color:#339999;">/*
	 * getInstance函数必须是static
	 */
</span>	public static Singleton getInstance(){
		return instance;
	}
	
	<span style="color:#339999;">/*
	 * 模拟单例类扮演其它角色
	 */
</span>	public static void createString(){
		System.out.println("createString in Singleton!");
	}
}

这样当使用Singleton.createString()执行任务是,程序输出:

Singleton is created!
createString in Singleton!

可以看到,虽然此时并没有使用到单例类,但它还是被创建出来了,这就是我们开发人员最不愿意看到的。为了解决这个问题,并以此提高系统在相关函数调用时的反应速度,就需要引入延迟加载机制

二、延迟加载实现方式

public class LazySingleton {
	
	<span style="color:#339999;">/*
	 * 单例类必须要有一个private访问级别的构造函数,
	 * 这样保证了单例不会在系统中的其他代码内被实例化
	 */
</span>	private LazySingleton(){
		<span style="color:#339999;">//创建单例模式的过程可能缓慢
</span>		System.out.println("LazySingleton is created!");
	}
	
	<span style="color:#339999;">/*
	 * instance成员变量必须是static,此时先不要初始化,用到时再进行初始化
	 */
</span>	private static LazySingleton instance = null;
	
	<span style="color:#339999;">/*
	 * getInstance函数必须是static
	 */
</span>	public static <strong>synchronized</strong> LazySingleton getInstance(){
		<span style="color:#339999;">//这里首先判断instance是否为null,如果是,则先初始化
</span>		if(instance == null){
			instance = new LazySingleton();
		}
		return instance;
	}
}

需要说明:getInstance()方法必须是同步的,否则在多线程环境下,当线程1正新建单例且完成赋值操作前,线程2可能判断instance为null,故线程2也将启动新建单例的程序,而导致多个实例被创建,故同步关键字是必须的。

优点:实现了延迟加载的功能;

缺点:和第一种方式相比,增加了同步关键字,导致在多线程环境中,它的时耗远远大于第一种方式。

@Override
	public void run(){
		Long begintime = System.currentTimeMillis();
		for(int i=0;i<10000;i++){
			Singleton.getInstance();
//			LazySingleton.getInstance();
		}
		System.out.println("spend:"+(System.currentTimeMillis()-begintime));
	}


开启5个线程同时完成以上代码,使用第一种类型单例耗时0ms,但使用第二种单例却相对耗时390ms,性能至少差了2个数量级。开启5个线程同时完成以上代码,使用第一种类型单例耗时0ms,但使用第二种单例却相对耗时390ms,性能至少差了2个数量级。开启5个线程同时完成以上代码,使用第一种类型单例耗时0ms,但使用第二种单例却相对耗时390ms,性能至少差了2个数量级。开启5个线程同时完成以上代码,使用第一种类型单例耗时0ms,但使用第二种单例却相对耗时390ms,性能至少差了2个数量级。开启5个线程同时完成以上代码,使用第一种类型单例耗时0ms,但使用第二种单例却相对耗时390ms,性能至少差了2个数量级。开启5个线程同时完成以上代码,使用第一种类型单例耗时0ms,但使用第二种单例却相对耗时390ms,性能至少差了2个数量级。开启5个线程同时完成以上代码,使用第一种类型单例耗时0ms,但使用第二种单例却相对耗时390ms,性能至少差了2个数量级。开启5个线程同时完成以上代码,使用第一种类型单例耗时0ms,但使用第二种单例却相对耗时390ms,性能至少差了2个数量级。

 

三、延迟加载改进实现方式

为了延迟加载而加入了同步关键字导致系统性能降低,有点得不偿失,我们可以对延迟加载的方式进行改进:

public class StaticSingleton {
	
	<span style="color:#339999;">/*
	 * 单例类必须要有一个private访问级别的构造函数,
	 * 这样保证了单例不会在系统中的其他代码内被实例化
	 */
</span>	<strong>private</strong> StaticSingleton(){
		<span style="color:#339999;">//创建单例模式的过程可能缓慢
</span>		System.out.println("LazySingleton is created!");
	}
	
	<span style="color:#339999;">/*
	 * 引入内部类维护单例类实例的初始化,当StaticSingleton被加载时其内部类不会被初始化,
	 * 故可以确保当StaticSingleton类被载入JVM时,不会初始化单例类实例。
	 */
</span>	<strong>private static class</strong> SingletonHolder{
		<strong>private static</strong> StaticSingleton instance = new StaticSingleton();
	}
	
	<span style="color:#339999;">/*
	 * getInstance函数必须是static,该方法调用了内部类的instance变量,导致内部类的加载,
	 * 并对单例类实例进行了初始化
	 */
</span>	public <strong>static</strong> StaticSingleton getInstance(){
		return SingletonHolder.instance;
	}
}

通过以上分析,由于单例类实例的建立是在类加载时完成的,故天生对多线程友好,从而getInstance方法不需要同步关键字。因此这种实现方式同时兼备以上两种实现方式的优点:既可以实现延迟加载,也不必使用同步关键字。

以上三种方式基本可以实现大部分情况了,但还有特殊情况的特殊解决方法,这里不再赘述,以上所写都参考了《Java程序性能优化  让你的Java程序更快、更稳定》一书,有兴趣的童鞋可以下载学习。

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值