关于单例模式你需要知道的都在这了
定义
单例模式确保一个类只有一个实例,并提供一个全局访问点
做法
把类的构造函数设置为private,用一个静态变量存储本身的唯一一个实例,然后通过静态方法获取唯一的实例。
应用场景
下面列举的应用场景引用于https://blog.csdn.net/tanyujing/article/details/14160941
- Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
- windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
- 网站的计数器,一般也是采用单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
- 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
- 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
- HttpApplication 也是单例模式的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.
单例模式与全局变量
全局变量也能提供全局访问点,但全局变量在程序一开始就要初始化,而单例模式可以选择在加载类的时候创建,也可以选择在需要实例的时候再创建。最重要的是,全局变量不能保证只有一个实例被创建。
单例模式的不同实现
下面介绍三种常见的单例模式的实现–饥汉式、懒汉式和双重检查加锁式,更多的实现可以参考
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时才可能有同步异常,所以每次都执行同步操作其实降低了性能。
双重检查加锁式(Double Checked Locking)
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式。单例模式的实现有利于提高系统性能,保证了实例的唯一性,但修改单例类时可能会对系统造成较大的影响。
转载:https://blog.csdn.net/qq_36267931/article/details/105655082