赞
踩
单例模式确保一个类只有一个实例,并提供一个全局访问点
把类的构造函数设置为private,用一个静态变量存储本身的唯一一个实例,然后通过静态方法获取唯一的实例。
下面列举的应用场景引用于https://blog.csdn.net/tanyujing/article/details/14160941
全局变量也能提供全局访问点,但全局变量在程序一开始就要初始化,而单例模式可以选择在加载类的时候创建,也可以选择在需要实例的时候再创建。最重要的是,全局变量不能保证只有一个实例被创建。
下面介绍三种常见的单例模式的实现–饥汉式、懒汉式和双重检查加锁式,更多的实现可以参考
https://www.cnblogs.com/zhaoyan001/p/6365064.html
EagerSingleton.java
package priv.mxz.design_pattern.singleton_pattern;
class EagerSingleton {
private static EagerSingleton instance=new EagerSingleton();
private EagerSingleton(){}
static public EagerSingleton getInstance(){
return instance;
}
}
在类加载的时候已经把实例初始化,在调用静态方法getInstance前已经有了具体的实例,如果程序一直没有使用这么实例,则会造成内存上的浪费。由于在类加载的时候已经初始化了实例,所以是线程安全的。
LazySingleton.java
package priv.mxz.design_pattern.singleton_pattern;
class LazySingleton {
private static LazySingleton instance;
private LazySingleton(){ }
public synchronized static LazySingleton getInstance(){
if (instance==null)
instance=new LazySingleton();
return instance;
}
}
在调用getInstance后才判断是否有具体的实例,如果没有则初始化一个,这样可以避免在实例还不被使用时占用内存。
代码中getInstance用了关键字synchronized,后者用于线程同步,保证同一时刻只有一个线程能运行getInstance方法且中途不能切换线程。如果没有了synchronized,那么上述代码就是线程不安全的,因为可能存在这样的情况:线程A和线程B都是第一次运行getInstance方法,首先线程A执行完if语句,判断出需要创建实例,然后准备创建实例,但此时CPU控制权切换到了线程B,此时线程B也执行if判断,也判断出需要创建实例,然后线程B创建了自己的实例并返回,切换回线程A,线程A也创建了自己的实例并覆盖线程B创建的instance并返回,这样线程AB得到了两个不同的instance。
加了synchronized后代码是线程安全的,但每次调用getInstance都需要执行同步操作,实际上只有第一次instance是null时才可能有同步异常,所以每次都执行同步操作其实降低了性能。
LazySingletonDCL.java
package priv.mxz.design_pattern.singleton_pattern; class LazySingletonDCL { private volatile static LazySingletonDCL instance; private LazySingletonDCL(){} public static LazySingletonDCL getInstance(){ if (instance==null){ synchronized (LazySingletonDCL.class){ if (instance==null){ instance=new LazySingletonDCL(); } } } return instance; } }
volatile关键字保证了当instance发生变化时(被创建为实例)多个线程能马上知道变化发生。避免实例已经被创建,另一个线程依然判断instance依然为null的情况。
getInstance方法中,跟懒汉式单例模式对比,DCL式把同步放到了判断instance为null后,如果instance不为null则不会有同步操作,提高了系统性能。
instance为null的情况下,线程先获得同步锁,拿到以后还要再次判断instance是否为null,是的话才创建实例,不是的话什么都不干。这么做的原因是两个线程A和B依然可能先后执行完第一个if判断,然后同时进入到同步部分,然后线程A获取到锁,然后创建了一个实例,随后锁交给线程B,此时在同步部分的代码里,instance已经不是null(线程B通过volatile知道线程A已经创建了一个实例),如果不再次判断,那么线程B还会创建一个实例。这就是需要双重检查的原因。
SingletonPattern.java
package priv.mxz.design_pattern.singleton_pattern; import java.lang.reflect.Field; import java.lang.reflect.Modifier; public class SingletonPattern { public static void beforeGetInstance(Class cls)throws IllegalAccessException{ Field[] fields=cls.getDeclaredFields(); if (fields!=null && fields.length>0){ for (Field field:fields){ field.setAccessible(true); if (field.getType()==cls && Modifier.isStatic(field.getModifiers())){ System.out.println("private instance of "+cls.getName()+ " before getInstance() is "+ field.get(cls)); } } } } public static void main(String[] args){ try { beforeGetInstance(EagerSingleton.class); } catch (IllegalAccessException e) { e.printStackTrace(); } try { beforeGetInstance(LazySingleton.class); } catch (IllegalAccessException e) { e.printStackTrace(); } try { beforeGetInstance(LazySingletonDCL.class); } catch (IllegalAccessException e) { e.printStackTrace(); } EagerSingleton es1=EagerSingleton.getInstance(); EagerSingleton es2=EagerSingleton.getInstance(); System.out.println("two EagerSingleton equal: "+ (es1==es2)); // EagerSingleton es3=new EagerSingleton(); 失败,无法编译通过 LazySingleton ls1=LazySingleton.getInstance(); LazySingleton ls2=LazySingleton.getInstance(); System.out.println("two LazySingleton equal:"+(ls1==ls2)); LazySingletonDCL lsd1=LazySingletonDCL.getInstance(); LazySingletonDCL lsd2=LazySingletonDCL.getInstance(); System.out.println("two LazySingleton DCL equal:"+(lsd1==lsd2)); } }
下面是结果
private instance of priv.mxz.design_pattern.singleton_pattern.EagerSingleton before getInstance() is priv.mxz.design_pattern.singleton_pattern.EagerSingleton@14ae5a5
private instance of priv.mxz.design_pattern.singleton_pattern.LazySingleton before getInstance() is null
private instance of priv.mxz.design_pattern.singleton_pattern.LazySingletonDCL before getInstance() is null
two EagerSingleton equal: true
two LazySingleton equal:true
two LazySingleton DCL equal:true
main函数中我们分别把三种单例模式的类的Class实例传进函数beforeGetInstance,beforeGetInstance通过反射的方式判断类中静态变量instance的值是否为null,以此来判断单例模式是饥汉式还是懒汉式(即判断在getInstance被调用之前instance是否已经初始化),从输出可以看到EagerSingleton在调用getInstance前已经初始化了实例,其他两个则没有,值为null
接着我们分别用三个类的getInstance各自创建了两个实例,判断着两个实例是否相同,从结果可知,三个类都实现了单例模式的基本功能–返回的两个实例是完全相同的唯一一个实例。
优点:
单例模式可以替代全局变量为程序提供唯一的全局访问点,避免了对象的多次的创建与销毁,提高了系统性能。
缺点:
程序中可能有许多地方依赖于单例类,单例类的职责过于繁重,修改单例类可能牵一发而动全身。
单例类有创建产品的工厂角色,也有产品本身的角色,违背了“单一职责”的设计原则。
单例类不方便继承,不利于扩展。
单例模式为外部提供了单例类唯一的类实例的访问入口,可以保证在程序运行期间只有一个单例类实例,常见的应用场景有日志记录器和垃圾回收器等,单例模式的实现有多种方式,比如饥汉式,懒汉式和DLC式。单例模式的实现有利于提高系统性能,保证了实例的唯一性,但修改单例类时可能会对系统造成较大的影响。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。