设计模式之单例模式

单例模式

所谓单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。在Java,一般常用在工具类的实现或创建对象需要消耗资源。

单例模式的特点

  1. 构造方法私有化
  2. 指向自己实例的私有静态引用
  3. 以自己实例为返回值的公共静态方法

为什么需要单例模式

许多时候,整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,显然,这种方式简化了在复杂环境下的配置管理。

特别地,在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。事实上,这些应用都或多或少具有资源管理器的功能。例如,每台计算机可以有若干个打印机,但只能有一个 Printer Spooler(单例) ,以避免两个打印作业同时输出到打印机中。再比如,每台计算机可以有若干通信端口,系统应当集中 (单例)管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。

综上所述,单例模式就是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种方法。

单例模式的实现方法

饿汉模式

饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。比较常用,但容易产生垃圾,因为一开始就初始化。一般用来初始化项目的时候加载配置文件等。

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    	return instance;  
    }  
}

懒汉模式

懒汉模式(线程不安全)

线程不安全,延迟初始化,严格意义上不是不是单例模式

public class Singleton {  
	//私有实例属性
    private static Singleton instance; 
    //私有构造方法 
    private Singleton (){}  
  	//获取实例方法
    public static Singleton getInstance() {  
	    if (instance == null) {  
	        instance = new Singleton();  
	    }  
    return instance;  
    }  
}
懒汉模式(线程安全)(不推荐)

在getInstance方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的。

public class Singleton {
 
    private static Singleton singleton;
 
    private Singleton(){}
 
    // 使用 synchronized 修饰,临界资源的同步互斥访问
    public static synchronized Singleton getInstance(){
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
懒汉模式(线程安全)(不推荐)

和上一种类似,每次调用都会加锁,效率不高

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getInstance() {  
    	//如果没有实例,则锁住当前类  
        synchronized (Singleton.class) {
        	//再次判断有无实例,加锁之前可能被其他线程先创建实例
        	if (singleton == null) {  
            	singleton = new Singleton();  
        	}  
        }  
    	return singleton;  
    }  
}
懒汉模式(线程安全)——双重检查锁定(推荐)

在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗。
请注意这里的关键词 volatile ,文末会说明为什么要加

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getInstance() {  
	    //先判断有没有实例化
	    if (singleton == null) {
	    	//如果没有实例,则锁住当前类  
	        synchronized (Singleton.class) {
	        	//再次判断有无实例,加锁之前可能被其他线程先创建实例
	        	if (singleton == null) {  
	            	singleton = new Singleton();  
	        	}  
	        }  
	    }  
    	return singleton;  
    }  
}
懒汉模式(静态内部类)

只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。

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

特殊的单例模式(枚举)

默认枚举实例的创建是线程安全的,并且在任何情况下都是单例。实际上
枚举类隐藏了私有的构造器。
枚举类的域 是相应类型的一个实例对象

public enum Singleton {
    INSTANCE;
}

枚举实例在日常开发是很少使用,就是很简单以导致可读性较差。

单例模式的优缺点

优点

  1. 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
  2. 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,比如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。
  3. 单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
  4. 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。

缺点

  1. 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
  2. 单例对象如果持有Context,那么很容易引发内存泄露,此时需要注意传给单例对象的Context最好是Application Context。

volatile

instance = new Singleton() 在JVM中会发生什么?

1. new一个对象是非原子操作的,会分为如下三个步骤

memory = allocate();       //1:分配对象的内存空间
ctorInstance(memory);      //2:初始化对象
singleton = memory;        //3:使singleton指向刚分配的内存地址

2. 实际上这个过程可能是无序的,有可能先分配内存地址然后初始化对象

memory = allocate();       //1:分配对象的内存空间
singleton = memory;        //3:使singleton指向刚分配的内存地址,此时singleton != null
ctorInstance(memory);      //2:初始化对象

重排序情景再现

在多线程的情况下,假设有两个线程,

  1. 线程1进入getInstance()方法;
  2. 此时singleton == null,线程1进入synchronized 代码块;
  3. 当给singleton分配内存地址后,并未完成初始化,此时singleton != null
  4. 线程2进入getInstance()方法, 此时singleton != nullreturn一个没有初始化的对象;
  5. 线程1完成初始化;
    一旦出现这种情况,线程2得到了没有完成初始化的对象,有可能导致灾难性的后果。我们只需使用volatile关键字修饰单例引用就可以避免上述灾难。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值