单例模式
本文参考了《Android源码设计模式解析与实战》。
定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
使用场景:确保某个类有且只有一个对象地场景,避免产生多个对象消耗过多地资源,或者某种类型地对象只应该有且只有一个。例如,创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源,这时就要考虑使用单例模式。
单例模式有很多实现方式,但是不管哪种实现方式它们的核心原理都是将构造函数私有化,并且都是通过静态方法获取一个唯一的实例,在这个获取过程中必须保证线程安全、防止反序列化导致重新生成实例对象等问题。
单例模式角色介绍:
(1)Client——高层客户端
(2)Singleton——单例类
实现单例模式主要由如下几个关键点:
(1)构造函数不对外开放,一般为private,使外部不能new出这个对象
(2)通过一个静态方法或者枚举返回单例类对象
(3)确保单例类的对象有且只有一个,尤其在多线程环境下
(4)确保单例类对象在反序列化时不会重新构建对象
通过将单例类的构造函数私有化,使得客户端代码不能通过new的形式手动构造单例类的对象。单例类会暴露一个公有静态方法,客户端需要调用这个静态方法获取到单例类的唯一对象,在获取这个单例对象的过程中需要确保线程安全,即在多线程环境下构造单例类的对象也是有且只有一个。
- 单例模式的简单实现:
package singleton;
public class Staff {
public void work(){
System.out.println("干活!!!");
}
}
public class VP extends Staff{
public void work(){
System.out.println("管理员工");
}
}
public class CEO extends Staff{
private static final CEO mCeo=new CEO();
//构造函数私有
private CEO(){
}
//公有静态函数,对外暴露获取单例对象的接口
public static CEO getCeo(){
return mCeo;
}
public void work(){
System.out.println("我是ceo");
}
}
public class Company {
private List<Staff>allStaffs=new ArrayList();
public void addStaff(Staff per){
allStaffs.add(per);
}
public void showAllStaffs(){
for(Staff per:allStaffs){
System.out.println("Obj:"+per.toString());
}
}
}
public class SingleTon {
public static void main(String[] args) {
// TODO code application logic here
Company cp=new Company();
Staff ceo1=CEO.getCeo();
cp.addStaff(ceo1);
Staff ceo2=CEO.getCeo();
cp.addStaff(ceo2);
cp.showAllStaffs();
}
}
运行结果:
- 单例模式的其他实现方式
1.懒汉模式
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
懒汉单例模式的优点是单例只有在使用时才会被实例化,在一定程度上节约了资源;缺点是第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance都进行同步,造成不必要的同步开销。
2.Double Check Lock(DCL)实现单例
public class Singleton{
private static Singleton sInstance=null;
private Singleton(){}
public void doSomething(){
System.out.println("do sth.");
}
public static Singleton getInstance(){
if(sInstance==null){
synchronized(Singleton.class){
if(sInstance==null){
sInstance=new Singleton();
}
}
}
return sInstance;
}
}
DCL的优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高。缺点:第一次加载时反应稍慢,也由于java内存模型的原因偶尔会失败。在高并发环境下也有一定有缺陷,虽然发生概率很小。DCL模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于JDK6版本使用,否则,这种方式一般能够满足需求。
3.静态内部类单例模式
public class Singleton{
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder{//该类之所以是static是因为sInstance是静态的,如果不设为静态会报错
private static final Singleton sInstance=new Singleton();//重点在于确定是哪个类的单例
}
}
当一次加载Singleton类时并不会初始化sInstance,只有在第一次调用Singleton的getInstance方法时才会导致sInstance被初始化。因此,第一次调用getInstance方法会导致虚拟机加载SingletonHolder类,这种方式不仅能够确保线程安全,也能保证单例对象的唯一性,同时也推迟了单例的实例化,所以这时推荐使用的单例模式实现方式。
4.枚举单例
package singleton;
public class DBConnection {}
public enum DataSourceEnum {
DATASOURCE;
private DBConnection connection = null;
private DataSourceEnum() {
connection = new DBConnection();
}
public DBConnection getConnection() {
return connection;
}
}
public class Main {
public static void main(String[] args) {
DBConnection con1 = DataSourceEnum.DATASOURCE.getConnection();
DBConnection con2 = DataSourceEnum.DATASOURCE.getConnection();
System.out.println(con1 == con2);
}
}
写法简单时枚举单例最大的优点,枚举在java中与普通的类一样,不仅能够有字段,还能够有自己的方法。最重要的时默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。
5.使用容器实现单例模式
public class SingletonManager{
private static Map<String,Object>objMap=new HashMap<String,Object>();
private SingletonManager(){}
public static void registerService(String key,Object instance){
if(!objMap.containsKey(key)){
objMap.put(key,instance);
}
}
public static Object getService(String key){
return objMap.get(key);
}
}
在程序的初始,将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。