##一、创建型模式--单例模式
单例模式介绍:
单例模式是应用最广的模式之一,也可能是很多初级工程师唯一会使用的设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例对象存在。许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。如在一个应用中,应该只需要有一个ImageLoader实例,这个ImageLoader中含有线程池、系统缓存、网络请求等,很消耗资源,因此,没有理由让它构造多个实例。这种不能自由构造对象的情况,就是单例模式的使用场景。
单例模式的定义:
确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式的使用场景:
确保某个类有且只有一个对象的情景,避免产生多个对象,消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如:创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源,这时候就需要考虑使用单例模式。
单例模式的关键点:
- 构造函数不对外公开,一般为private
- 通过一个静态方法或者枚举返回单例类对象
- 确保单例类的对象有且只有一个,尤其是在多线程下。
- 确保单例在反序列化时不会重现构建对象。
单例模式的简单示例:
//普通员工
publi class Staff{
public void work(){
//干活
}
}
//副总裁
publi class VP extends Staff{
@Override
public void work(){
//管理下面的经理
}
}
//CEO
publi class CEO extends Staff{
private static final CEO mCeo = new CEO();
//私有化构造方法
private CEO(){
}
public static CEO getCeo(){
return mCeo;
}
@Override
public void work(){
//管理下面的VP
}
}
//公司类
public class Company{
public List<Staff> allStaffs = new ArrayList();
public void addStaff(Staff person){
allStaffs.add(person);
}
public void showAllStaffs(){
for(Staff staff:allStaffs){
System.out.println("Obj:"+staff.hashcode());
}
}
}
public class Client {
public static void main(String[] args) {
Conpany cp = new Conpany();
//CEO只能通过getCeo()函数获取
Staff ceo1 = CEO.getCeo();
Staff ceo2 = CEO.getCeo();
//通过new创建VP对象
Staff vp1 = new VP();
Staff vp2 = new VP();
//通过new创建Staff对象
Staff staff1 = new Staff();
Staff staff2 = new Staff();
Staff staff3 = new Staff();
cp.addStaff(ceo1);
cp.addStaff(ceo2);
cp.addStaff(vp1);
cp.addStaff(vp2);
cp.addStaff(staff1);
cp.addStaff(staff2);
cp.addStaff(staff3);
cp.showAllStaffs();
}
}
每个对象都有一个唯一的hashcode值,打印结果请自行测试。
从上述代码中可以看到,CEO类不能通过new的形式来创建,只能通过CEO.getCEO()函数来获取,而这个CEO对象是静态对象,并且在声明的时候就已经初始化,这就保证了CEO对象的唯一性。从输出结果可以得知两次输出的CEO对象是一样的,而VP、Staff等类型的对象都是不同的,这个实现的核心在于将CEO类的构造方法私有化,使得外部程序不能通过构造方法来构造CEO对象,而CEO类通过一个静态方法返回一个静态对象。
单例模式-懒汉模式:
懒汉模式是声明一个静态对象,并且在用户第一次调用getInstance时进行初始化。而上述的饿汉式(CEO类)是在声明静态对象时就已经初始化。懒汉单例模式的实现代码如下。
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){
instace = new Singleton();
}
return instance;
}
}
总结:getInstance()方法中添加了synchronized关键字,也是就是说getInstance()是一个同步方法,这样在多线程下保持了单例对象唯一性。但是存在一个问题,即使是instance已经被初始化(第一次调用就初始化好了),每次调用getInstance()时都会进行同步,消耗不必要的资源,这也是懒汉单例模式存在最大的问题。
最后:懒汉单例模式优点时只有在需要使用的时候才初始化,在一定程度上节省资源;缺点是第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次getInstance都进行同步,造成不必要的同步开销,这种模式一般不建议使用。
单例模式-Double CheckLock实现单例:
DCL方式实现单例模式的优点是既能够在需要的时候才初始化单例,又能够保证线程安全,且单例对象初始化之后调用getInstance不进行同步锁。代码如下:
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null)
instace = new Singleton();
}
}
return instance;
}
}
单例模式-枚举单例:
更为简单的单例实现方式:
public enum SingletonEnum{
INSTANCE;
public void dosomething(){
system.out.println("do sth.");
}
}
枚举在java中和普通类是一样的,不仅有字段,还有自己的方法。最重要的是枚举默认是线程安全的。
⚠️通过序列化可以将一个单例的实例对象写到磁盘,然后再读回来从而有效的获得一个实例。即使构造函数是私有的,反序列化时然后可以通过特殊的途径去创建一个新的实例,相当于调用该类的构造方法。反序列化操作提供一个很特殊的钩子函数,类中具有一个私有的、被实例化的方法readResolve(),这个对象可以让开发人员控制对象的反序列化。例如:上述几个示例如果要杜绝反序列化时重新生成新对象,那么必须加入如下方法:
private Object readResolve() throw ObjectStreamException{
return sInstance;
}
也就是说在readResolve()中将sInstance对象返回,而不是重新生成一个新的对象。对于枚举,并不存在这个问题,因为即使反序列化也不会重新生成新的实例。
单例模式-使用容器实现单例模式:
直接贴代码
public class SingletonManager{
private static Map<String,Object> objMap = new HashMap();
private Singleton(){}
public static void registerService(String key,Object instance){
if(!objMap.containKey(key)){
objMap.put(key,instance);
}
}
public static Object getService(String key){
return objMap.get(key);
}
}
在程序开始时,将许多单例类型注入到一个统一的管理类中,在使用的时候更具key获取对象对应类型的操作。这种方式可以使得我们管理多种类型的单例,并且在使用的时候通过统一的接口进行操作,降低用户的使用成本,也对用户隐藏了具体细节,降低耦合度。
###扩展
Android源码中单例模式的探索
在Android开发中,我们经常使用context.getSystemService(Context.*)来获取系统级别的服务。如 WindowsMangerService、ActivityManagerService、LayoutInflater等。接下来,我们以LayoutInflater为例来说明
未完待遇….
Create by Diamond_Lin
2016年8月21日。