单例模式
什么是单例模式
单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。
单例模式确保某个类只有一个实例,
实现步骤
- 将该类的构造方法定义为私有方法,这样其他的代码就不能直接调用该类的构造方法来实例化对象,只有通过该类提供的静态方法来得到这个实例
- 在该类提供一个静态方法,当我们调用这个方法的时候,如果类持有哦的引用不为空,就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用
特点
- 单例类只有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类必须给所有其他对象提供这一实例
应用场景
-
window桌面的回收站,我们试图打开一个新的回收站,window不会再打开一个回收站,也就是说,系统只维护一个回收站的实例
假设每次创建回收站会消耗很多的资源,而每个回收站的资源共享,在没有必要创建该实例的情况下,创建多个实例,会对系统造成不必要的负担,造成资源的浪费
-
网站的计时器也是一个单例模式实现的例子。如果有多个计数器,每一个用户访问都会去刷新计数器的值,计数器的值就很难做到同步的效果,但是采用单例就不会存在这样的问题,并且还可以避免线程的安全问题
-
还有比如:多线程的连接池设计、web开发读取配置文件(HttpApplication)
单例模式优缺点
根据上面的例子,总结以下优缺点:
优点
- 在内存中只有一个对象,节省空间;
- 避免频繁的创建和销毁对象,提高性能
- 比米娜对共享资源的多重占用,简化访问
- 为整个系统提供一个全局访问点
缺点
- 不适合变化频繁的对象
- 滥用单例带来的一些问题,如为了节省资源将对数据库连接池对象设计为的单例类,可能导致共享连接池对象的程序过多而出现的连接池溢出问题
- 如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失
分类
饿汉式
public class danli {
//指向自己实例的私有静态引用,主动创建
private static danli danli=new danli();
//私有的构造方法
private danli(){}
//以自己实例返回值的静态的公有方法,静态工厂
public static danli getdanli(){
return danli;
}
}
类的方式需要加载,所有上述的类只能加载一次。因此,上面的类被加载的时候,就会实例化一个对象交给自己引用,供系统使用
优点:写法简单,避免线程同步
缺点:达不到Lazy Loading的效果,如果是总用不到这个实例,就会造成内存的浪费
懒汉式
public class danli1 {
//指向自己实例的私有静态引用
private static danli1 danli1;
//私有的构造方法
private danli1(){}
//以自己实例为返回值的静态的公有方法,静态工厂方法
public static danli1 getDanli1(){
//被动创建,在真正需要使用时才去创建
if(danli1==null){
danli1=new danli1();
}
return danli1;
}
}
此单例模式被延迟加载,只有真正使用的时候才会实例化一个对象给自己引用
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果是多线程,一个线程刚进入if判断语句没来得及往下执行,另一个也进入这个判断语句,会产生多个实例。因此多线程环境不可用。
静态初始化
//阻止发生派生,而派生可能会增加实例
public sealed class chushi{
//在第一次引用类的任何成员创建实例,公共语言运行库负责处理变量初始化
private static readonly chushi instance=new chushi();
private chushi(){
public static chushi GetInstance(){
return instance;
}
}
}
这种方法不需要开发人员显示的编写线程安全代码,可以解决多线程环境下他是不安全问题。
双重加锁机制
public class jiasuo
{
private static jiasuo instance;
//程序运行时创建一个静态只读的进程辅助对象
private static readonly object syncRoot = new object();
private jiasuo() { }
public static jiasuo GetInstance()
{
//先判断是否存在,不存在再加锁处理
if (instance == null)
{
//在同一个时刻加了锁的那部分程序只有一个线程可以进入
lock (syncRoot)
{
if (instance == null)
{
instance = new jiasuo();
}
}
}
return instance;
}
}
使用双重检测同步延迟加载去创建单例的做法是一个非常优秀的做法,其不但保证了单例,而且切实提高了程序运行效率
优点:线程安全;延迟加载;效率较高。
总结
单例模式的实现方法还有很多。但是,这四种是比较经典的实现,也是我们应该掌握的几种实现方式。
必须注意以下两点:
- 尽量减少同步块的作用域;
- 尽量使用细粒度的锁。