设计模式三之单例模式
有一些对象,我们只需要一个,比如:线程池,缓存,对话框,处理偏好设置和注册表的对象等等。
这些对象只能有一个实例,如果制造出多个实例,就会产生bug。
全局变量和单例的区别:
(1) 全局变量必须在程序一开始就创建好对象,如果这个对象非常耗费资源,而程序在本次执行中没有用到这个变量,则会造成资源的浪费。
(2) 单例模式可以实现在需要用到的时候再创建该对象,且保证整个程序中只创建一次。
接下来,我们来分析单例模式的最经典实现:
public class Singleton { //私有, 静态变量 , 记录Singleton的唯一实例 private static Singleton uniqueInstance; //私有的构造方法,只有在Singleton类内才可以调用构造器 private Singleton(){} //静态方法,在此方法中实例化对象 public static Singleton getInstance() { //延迟实例化,如果我们不需要这个实例,就永远不会创建,保证资源不被浪费 if(uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } }
注意:单例模式的核心就在于拥有一个私有的构造方法,任何人想要创建实例,都不能直接创建,而是必须向 Singleton 做出请求,在 Singleton 类中进行判断,没有实例才进行创建,有的话直接返回。
现在我们可以尝试定义单例模式:确保一个类只有一个实例,并且提供一个全局访问点。
但是到这里,请注意,上述所有的都是针对于单线程来操作的,对于多线程来讲,可能会创建出多个实例。但是我们必须潜意识中认为所有程序都是多线程的,怎样保证多线程安全问题呢?有3种方式,各有优缺点,我们接下来进行分析:
方法一:将 getInstance() 方法变为同步 (synchronized) 的。
public class Singleton { private static Singleton uniqueInstance; private Singleton(){} //加入 synchronized 关键字 public static synchronized Singleton getInstance() { if(uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } }
加入 synchronized 关键字之后,迫使每个线程在进入 getInstance() 方法的时候,没有线程在此方法中,也就是说,不会有两个线程同时进入该方法,从而保证线程安全。
但是此种处理方法的缺点在于,同步会导致很严重的性能问题,导致性能下降100倍。并且,只有在第一次执行此方法时,才需要同步(创建对象),以后执行的时候已经创建过对象,不需要同步。只是单纯的同步,会造成累赘。
方法二:放弃延迟化创建对象
public class Singleton { //放弃延迟化创建对象,在JVM加载此类的时候直接创建 private static Singleton uniqueInstance = new Singleton(); private Singleton(){} public static Singleton getInstance() { return uniqueInstance; } }
这样做保证了线程安全,但是又无法做到延迟化创建对象,如果在整个程序中一直用不到此对象,则会造成资源的浪费,和全局变量的效果类似。
方法三:利用双重检查加锁
public class Singleton { //加入 volatile 关键字 private volatile static Singleton uniqueInstance; private Singleton(){} public static Singleton getInstance() { //在这里进行双重判断,只有第一次执行的时候才创建实例,且保证线程安全 if(uniqueInstance == null) { synchronized(Singleton.class) { if(uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
第三种方法可以避免性能上的影响,并且可以保证线程安全,但是此种方法不能适用于1.4以及更早的java版本。
综合以上分析,单例模式是由类管理自己的实例的一种典型的体现,单例类不仅负责管理自己的实例,还在应用程序中担当角色,可以看成是承担了两种责任,看起来像违背了"一个类,一个责任“的原则,但是在 java 中,这种方式很常见,也很特殊。并且,其实适合用单例模式的机会并不是很多。
单例模式的核心在于,私有构造方法,静态方法和静态变量。
就这些,祝好运。