生活中我们一定存在这样一种场景,某件东西只能存在唯一的一个,比如天上的太阳,如果存在两个太阳,地球人估计已经没了。。。再比如我们经常听到的那句话:“世上不存在两片完全相同的叶子”,事实上,以上这两种情况,在软件设计领域,也到处可见。有时,对于某个系统中的具体某个类而言,保证只有一个实例是非常必要的,如windows系统中,只能打开一个资源管理器,因为打开两个资源管理器是没有意义的。如何保证一个类只能有一个或特定数量个实例呢?GoF这四个大牛级人物,早已经为我们准备好了解决方案:单例模式。
单例模式:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。
单例模式是结构最简单的设计模式,使用单例模式可以确保系统的一个类只有一个实例而且该实例易于被外界访问。
单例模式分类
有人形象地将单例模式分为两大类:饿汉式与懒汉式,为什么称作饿汉式与懒汉式呢?接着看。。
1.饿汉式
饿汉式单例模式实现代码:
1 public class Singleton { 2 3 private static Singleton instance = new Singleton(); 4 5 private Singleton() { 6 } 7 8 public static Singleton getSingleton() { 9 return instance; 10 } 11 }
在类Singleton中,构造函数被私有化,即外界环境不能通过构造函数来实例化一个Singleton对象,Singleton提供了一个私有化变量instance,并向外界提供了一个访问此成员变量的方法getSingleton(),在Singleton类被加载时,就将自己实例化(类似于一个饿汉,饭刚做好就急忙开吃了,因为实在是饿的不行啦,哈哈)。
2.懒汉式
懒汉式单例模式实现代码:
1 public class Singleton { 2 3 private static Singleton instance = null; 4 5 private Singleton(){ 6 7 } 8 9 10 public static synchronized Singleton getSingleton(){ 11 12 if(singleton == null){ 13 instance = new Singleton(); 14 } 15 return instance ; 16 } 17 18 }
懒汉式与饿汉式不同,在类加载时,并未将自己实例化,而是等第一次调用getSingleton()时才将自己实例化(类似于一个懒汉,虽然饭做好了,但是非要等人把饭端到自己面前,才肯开吃...),这种技术也被叫做延迟加载(Lazy Load)技术,为了避免在多个线程同时调用getSingleton(),要使用synchronized关键字进行修饰。使用synchronized关键字虽然可以避免多个线程同时调用getSingleton方法,但每次调用getSingleton方法,都要进行加锁操作,性能会大受影响。
接着对上述方案进行改造,通过观察发现,没有必要对整个getSingleton方法进行同步操作,只需对“instance = new Singleton”进行锁定,改造后的代码:
1 public class Singleton { 2 private static Singleton instance = null; 3 private Singleton(){ 4 5 } 6 public static synchronized Singleton getSingleton(){ 7 if (instance == null){ 8 synchronized (Singleton.class){ 9 instance = new Singleton(); 10 } 11 } 12 return instance; 13 } 14 }
仔细分析发现,上述代码仍然存在着问题,假如存在两个线程,线程A和线程B同时调用getSingleton方法,第七行代码instance == null对于两个线程而言,均成立,假设线程A先获得到锁,并进行实例化操作,当线程A执行完毕后,线程B并不知道Singleton已经被实例化,所以线程B将继续实行instance = new Singleton();此时将导致多个Singleton对象,违背了单例模式的设计思想。如何让线程B“知道”Singletong已经被实例化了呢?只需要在synchronized中再进行一次instan == null判断即可解决:
1 public class Singleton { 2 3 private static Singleton instance = new Singleton(); 4 5 private Singleton() { 6 } 7 8 public static synchronized Singleton getSingleton(){ 9 if (instance == null){ 10 synchronized (Singleton.class){ 11 if (instance == null){ 12 instance = new Singleton(); 13 } 14 } 15 } 16 return instance; 17 } 18 }
饿汉式与懒汉式的比较
饿汉式与懒汉式各有优缺点,饿汉式在类被加载时就将自己实例化,无需考虑多线程问题,但无论系统是否需要该对象,饿汉式都会进行实例化,因此资源利用率不如懒汉式。懒汉式在第一次使用时创建,无需一直占用系统资源,但是必须考虑多线程同时调用getSingleton方法的问题。
饿汉与懒汉的结合
饿汉式不能实现延迟加载,懒汉式需要考虑多线程问题,利用java技术中的IoDH(Initialization on Demand Holder)技术,可以有效地将二者结合起来使用,代码如下:
1 public class Singleton{ 2 private Singleton(){ 3 4 } 5 private static class HolerClass{ 6 private final static Singleton instance = new Singleton(); 7 } 8 public static Singleton getInstance(){ 9 return HolerClass.instance; 10 } 11 }
在Singleton类中,增加一个静态内部类,在该静态内部类中创建单例对象,通过getInstance方法将该实例返回给外界使用。由于静态内部类中的单例对象没有作为Singleton成员变量直接实例化,因此在类加载时不会实例化SIngleton,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个stati类型的变量instance,此时会首先实例化这个成员变量,由java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。
通过IoDH技术,既可以实现延迟加载,又可以保证线程安全,不影响系统性能,是一种比较好的java语言单例模式的实现方式,遗憾的是,出java语言外,很多面向对象语言并不支持IoDH技术。
参考资料:《Java设计模式》刘伟著