设计模式5 单例模式

定义

单例模式(Singleton Pattern)确保一个类只有一个实例,并提供一个全局访问点。——《HEAD First 设计模式》

主要角色

一个只能有一个实例的类:
请添加图片描述
Singleton类定义了一个getInstance操作,允许客户端访问它的唯一实例,getInstance是一个静态方法,主要负责创建和返回自己的唯一实例。而构造函数是private的,其他类无法访问到。

例子

源自《HEAD First 设计模式》的巧克力工厂。
巧克力工厂里面只有一个锅炉进行工作,不能创建其他锅炉实例。

public class ChocolateBoiler{
  private static Singleton uniqueInstance;
  private boolean empty;
  private boolean boiled;
  //构造函数声明为私有的,只有类内部才能访问
  private ChocolateBoiler(){empty = true;boiled = false;}
  //实例化对象并返回实例
  public static ChocolateBoiler getInstance(){
    if(uniqueInstance==null){
      uniqueInstance=new ChocolateBoiler();
      }
     return uniqueInstance;
  }
  //其他方法
}

这样调用getInstance()可以获取到巧克力锅炉的实例,如果没有就在getInstance()里面进行创建。那么如果我们不去使用这给类,那么这个对象可能永远不会实例化,这种情况叫做延迟实例化,这种实例方式也叫懒汉式(Lazy Loading)。
但是这种情况下显然是无法实现多线程运行的。

多线程

上面介绍的懒汉式是只能用于单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
还有几种单例模式的实现方式:饿汉式、双重加锁机制、静态内部类

饿汉式

public class Singleton{
    // 指向自己实例的私有静态引用,主动创建
    private static Singleton singleton = new Singleton();
    // 私有的构造方法
    private Singleton(){}
    // 以自己实例为返回值的静态的公有方法,静态工厂方法
    public static Singleton getSingleton(){
        return singleton;
    }
}

饿汉式单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。但是,如果不用这个实例,也会进行实例化,造成一定的浪费。

双重加锁机制

利用双重检查加锁(double-checked locking),首先检查是否实例已经创建了,如果未创建,才进行同步。

public class Singleton{
  //volatile关键字确保:当uniqueInstance变量被初始化成Singleton实例时,多个线程正确的处理uniqueInstance变量
  private volatile static Singleton uniqueInstance;
  //其他有用实例化变量
  private Singleton(){}
  public static  Singleton getInstance(){
  //检查实例,如果不存在,就进入同步区域。只有第一次才彻底执行这些代码。
    if(uniqueInstance==null){  
       synchronized (Singleton.class){
       //再次检查,如果仍是null才创建实例
          if(uniqueInstance==null){
              uniqueInstance=new Singleton();
          }
        }
     return uniqueInstance;
  }
  //其他方法
}

这样做既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。

静态内部类

静态内部类的方式效果类似双检锁,但实现更简单。只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式延迟加载。

public class Singleton {  
    private static class LazyHolder {  
       private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
       return LazyHolder.INSTANCE;  
    }  
}  

枚举

上面实现单例的方式都需要额外的工作来实现序列化,而且可以使用反射强行调用私有构造器。采用枚举可以避免这个问题。

public enum Singleton {
    instance;
    public static void dosomething() {
    }
}

使用时可以采用Singleton.instance

优缺点

优点

内存中只有一个实例,减少了内存开支,尤其一个对象需要频繁地创建销毁,而此时性能又无法优化时,单例模式的优势就非常明显。
避免对资源的多重占用(比如写文件操作,只有一个实例时,避免对同一个资源文件同时写操作),简单来说就是对唯一实例的受控访问。
在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。

缺点

没有接口,不能继承:实际上可以使用单例模式的机会并不是太多,构造函数是私有的导致它不能继承,如果改成public或者protected,继承之后会导致所有的派生类共享一个实例变量(因为instance是静态的)。
与单一职责冲突:一个类应该只做一件事情,但是单例模式在提供功能的同时还在负责管理自己的实例。

参考

简说设计模式——单例模式
深入理解设计模式(一):单例模式
单例模式的五种写法
JAVA设计模式之单例模式
HeadFirst 设计模式 5 单例模式(巧克力工厂)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值