设计模式之单例模式

单例模式

本文参考了《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获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值