概述
单例模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供。避免产生多个对象消耗过多的资源,比如数据库的操作,网络请求
优点是:在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
缺点是:没有抽象层,因此扩展很难。职责过重,在一定程序上违背了单一职责
饿汉式
volatile:线程在每次使用变量的时候,都会读取变量修改后的最的值
饿汉式:应用启动时加载并初始化,例如:Runtime类采用的就是饿汉式单例
public class SingleTon {
private SingleTon() { }
private static SingleTon instance = new SingleTon();
public static SingleTon newInstance(){
return instance;
}
}
懒汉式
在获取实例对象的时候才创建
public class SingleTon {
private SingleTon() { }
private static SingleTon instance = null;
public static SingleTon newInstance(){
if (instance == null){
instance = new SingleTon();//延迟加载
}
return instance;
}
}
多线程下的单例模式
懒汉式同步锁
public class SingleTon {
private SingleTon() { }
private static SingleTon instance = null;
public static SingleTon newInstance(){
synchronized (SingleTon.class){
if (instance == null){
instance = new SingleTon();//延迟加载
}
}
return instance;
}
}
双重校验锁
public class SingleTon {
private SingleTon() { }
private static SingleTon instance = null;
public static SingleTon newInstance(){
if (instance == null){//第一次检查
synchronized (SingleTon.class){
if (instance == null){//第二次检查
instance = new SingleTon();//延迟加载
}
}
}
return instance;
}
}
静态内部类:延迟加载,线程安全
因为在JVM进行类加载的时候他会保证数据是同步的,所以我们可以这样实现单例模式:采用内部类,在这个内部类里面去创建对象实例。这样的话,只要应用中不使用内部类 JVM 就不会去加载这个单例类,也就不会创建单例对象,从而实现「懒汉式」的延迟加载和线程安全。
public class SingleTon {
private static class SingleTonHolder{
public static SingleTon instance = new SingleTon();
}
private SingleTon() { }
public static SingleTon newInstance(){
return SingleTonHolder.instance;
}
public void doSomething(){}
}
枚举类型单例模式
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
public enum SingleTon {
instance;
public void doSomething(){}
}
SingleTon instance = SingleTon.instance;
instance.doSomething();
单例模式的优点
- 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
- 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(在Java EE中采用单例模式时需要注意JVM垃圾回收机制)。
- 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
单例模式的缺点
- 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。当然,在特殊情况下,单例模式可以实现接口、被继承等,需要在系统开发中根据环境判断。
- 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象
- 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中
单例模式的使用场景
在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”,可以采用单例模式,具体的场景如下:
- 要求生成唯一序列号的环境
- 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的
- 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)
经典案例
在Android系统中,我们经常会通过Context获取系统级别的服务,如WindowsManagerService,ActivityManagerService等,更常用的是一个LayoutInflater的类,这些服务会在合适的时候以单例的形式注册在系统中,在我们需要的时候就通过Context的getSystemService(String name)获取。还有Runtime类,采用饿汉式单例模式
需要注意的是:单例对象如果持有Context,那么很容易引发内存泄露。此时需要注意传递给单例对象的Context最好是Application Context