其实网上已经有很多介绍单例模式甚至是所有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程序更快、更稳定》一书,有兴趣的童鞋可以下载学习。