java设计模式之单例设计模式

单例设计模式

在开发时,有些对象只需要一个,比如说:

程序中经常需要读取的某个配置文件、常用工具类、线程池、缓存、日志对象等等

这些对象,如果被创造出多个实例,就会导致许多问题,比如占用过多的资源,不一致的结果,等等。

所以说,单例模式的目的就是:

保证创建对象实例,有且只有一个。

饿汉式:

1.将默认构造方法私有化,这样外界就无法直接创建类的实例

2.创建本类的唯一实例。

3.实例已经创建,那么外部怎么获取到这个唯一实例呢?将该实例静态化,但是为了控制访问,保证成员变量安全,将该实例私有化。

4.接着,提供一个public的获取该实例的方法,并且该方法静态化,否则无法直接获取。

那么饿汉式,为什么叫饿汉式呢?是因为,这种单例中,在类被加载时,就已经创建了该实例。

懒汉式:

1.将默认构造方法私有化,这样外界就无法直接创建类的实例

2.声明类的唯一实例,使用private static修饰,注意哦,这里是声明,但是并没有创建实例

3.提供一个用于获取实例的方法,使用public static修饰。该方法中,先判断该类的唯一实例是否为空,也就是判断该实例有没有被创建过,如果被创建过,那么返回该实例,如果没有被创建过,那么创建该实例,也就是new Single();

 

package com.gome.util;
class Single1{  //私有化默认构造函数 
	private Single1(){  }   //在本类中创建自己唯一的实例 
	private static Single1 instance = new Single1();    //写一个方法,供外界调用,得到这个唯一的实例 
	public static Single1 getInstance(){        
		return instance;    
	}
}
class Single2{    
	private Single2(){  }   
	private static Single2 instance;    
	public static Single2 getInstance(){        
		if(instance==null){         
			instance = new Single2();       
		}       
			return instance;    
	}
}
public class Test{    
	public static void main(String[] args){     
		Single1 s1 = Single1.getInstance();     
		Single1 s2 = Single1.getInstance();     
		if(s1==s2){         
			System.out.println("s1与s2是同一个实例");      
		}       
		Single2 s3 = Single2.getInstance();     
		Single2 s4 = Single2.getInstance();     
		if(s3==s4){         
			System.out.println("s3与s4是同一个实例");      
		}   
	}
}


 

懒汉式与饿汉式区别:

饿汉式的特点是,加载类时比较慢,因为有创建实例的过程,但运行时获取对象的速度比较快。线程安全

 

 

懒汉式的特点是加载类时比较快,但是运行获取对象速度比较慢,线程不安全。

那么,问题来了?

如何才能使得其线程安全呢?首先我们想到的应该是,使用synchronized关键字,在getInstance方法上,比如:

public static synchronized Single getInstance(){}

但是这样的话,确实解决了线程安全的问题,但是却降低了性能,有很多不必要的同比,比如return操作,需要同步的只是创建实例的操作。

 

改进下的话,可以:

private volatile static Single instantce = null;

实例对象用volatile修饰,具有synchronized可见性,在每次使用变量时,会读取该变量的最新值。这样线程会自动发现该变量的最新值,这样一个线程实例化成功,其他线程就会立即发现。

 

 

综上所述,简单的单例模式,并不是完全可靠的,主要有两个因素在影响

1.       通过java的反射机制完全可以实例化构造方法为private的类,这样便使得java单例的实现失效

2.       考虑到线程的存在,在并发环境下,很可能会出现多个实例。

以上只是介绍单例的基本使用方式,其实单例还有很多种

比如:登记式单例

示例代码如下:

import java.util.Map;
//登记式单例模式
public class RegisterSingle{  
	/**  
	* 使用静态代码块,在类被加载的时候,就将类的实例注册进一个map中,
	使用的时候,通过getInstance方法,传入类名   * 获取该类的实例   
	*/ 
	
	//定义一个map用来存放类的实例   
	private static Map map = new HashMap(); 
	static{     
		RegisterSingle single = new RegisterSingle();       
		map.put(RegisterSingle.class.getSimpleName(), single);  
	}   
	//私有化构造函数   
	private RegisterSingle(){   
	}   
	//写一个静态方法,供外部得到该类的唯一实例  
	public static RegisterSingle getInstance(String name){      
		if(name == null){           
			name = RegisterSingle.class.getSimpleName();            
			System.out.println("name == null===> name="+ name);     
		}       
		if(map.get(name) == null){          
			try {               
				map.put(name, (RegisterSingle) Class.forName(name).newInstance());          
			} catch (InstantiationException e) {                                
				e.printStackTrace();            
			} catch (IllegalAccessException e) {                                
				e.printStackTrace();            
			} catch (ClassNotFoundException e) {                                
				e.printStackTrace();            
			}       
		}       
		return map.get(name);   
	}
}


使用静态内部类的方式实现单例(理论上安全而且延迟加载)

另外一种完美实现的实现既线程安全又延迟加载的模式(Initialization on demand holder)使用静态内部类 

Public class Singleton{    Private Singleton(){};    Public static class Singleton1{        Private static final Singleton instance = new Singleton();}        Public static Singleton getInstance(){        Return Singleton1.instance;}}


 

这样就能保证在第一次调用getInstance()方法时,才会去初始化instance实例,而且该实例被定义为static,只会被初始化一次。

 

tips1

还有一个问题就是单例化类的序列化问题:如果单例类实现了serializable接口,这是要特别注意以为在默认情况下,每次反序列化时总会创建一个新的对象,注意系统就会出现多个对象了。解决方法:根据序列化可知:每次反序列化完成前都会去调用readResolve()方法,那就在该方法中,将用原对象取代新创建出来的对象。在是在该实现了序列化的类中再定义一个方法:

 

Public Singleton readResolve(){    sReturn instance;                   // instance是唯一的实例对象}








 

你可能感兴趣的:(java设计模式)