设计模式(一)单例模式

1、基本概念

一、单例基本概念

定义:java

  • 确保某个类有且只有一个实例对象

优势:数据库

  • 内存中只有一个实例,可减小内存使用,尤为是须要频繁建立、销毁对象的场景,单例模式比较有优点。
  • 避免对资源的多重占用,好比读配置、写文件等操做,只有一个对象去操做资源,避免了多个内存对象对资源的同时操做。
  • 单例可设置全局的访问点,共享资源访问。

缺点:设计模式

  • 扩展比较困难,通常单例没有接口
  • 单例模式跟单一职责原则冲突,单例会将多种业务逻辑放在一个类中。
  • 单例对象若是持有Context,容易引发内存泄露,最好传入ApplicationContext

使用场景:安全

  • 项目中须要一个共享访问点或共享数据,例如全局配置文件、数据库管理类、网络请求管理类等。
  • 建立对象须要消耗资源比较多,例如访问IO、数据库等资源时。

关键点:bash

  • 构造函数不对外开发
  • 确保多线程环境下对象只有一个

二、非线程安全的单例:

public class Singleton {
	private static Singleton singleton = null;
	
	private Singleton(){

	}

	public static Singleton getInstance(){
		if(singleton == null) {
			singleton = new Singleton();
		}
		return singleton;
	}
}
复制代码

若是两个线程同时执行到singleton == null 的判断的时候,两个线程条件都知足,会出现建立两个对象的状况,违反了单例只建立一个对象的原则。网络

2、线程安全的单例

一、饿汉式

public class Singleton {
    private static Singleton singleton = new Singleton();

    private Singleton() {

    }

    public static Singleton getInstance() {
        return singleton;
    }
}
复制代码
  • 实现原理:经过static修饰Singleton,静态变量随着类加载时完成初始化,在内存中只会有一个,全部没有线程安全的问题。
  • 缺点:先将对象建立出来,并无实际的调用,会形成没必要要的内存消耗。

二、懒汉式(同步方法)

public class Singleton {
    private static Singleton singleton = null;
    private Singleton(){

    }
    public synchronized static Singleton getInstance(){
        if(singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
复制代码
  • 实现原理:getInstance经过synchronized关键字进行修饰,保证只有一个线程执行getInstance方法内的建立逻辑,保证建立出来的对象只有一个。
  • 优势:用的时候才进行建立,减小没必要要的内存消耗。
  • 缺点:synchronized关键字存在效率问题,线程A执行到getInstance方法时,线程B只能处于等待状态,只有等线程A执行完getInstance方法,线程B才能继续执行。

三、懒汉式(双重检查加锁)

public class Singleton {
    private static volatile Singleton singleton = null;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
复制代码
(1)双层判断的做用
  • 外层singleton == null 判断为true,才执行synchronized所修饰的内部逻辑,不然直接获取singleton对象,保证须要有对象建立就不须要等待。
  • 内层singleton == null 判断是为了再次检查对象是否建立。
    为何:线程A执行到synchronized修饰的代码时,此时线程B正在执行内部逻辑,因此线程A只能处于等待状态。线程B执行完毕并将对象建立出来了,而线程A是不知道的,会再次建立,因此须要再次判断对象是否为空。
(2)volatile的做用

singleton = new Singleton() 不是原子操做,这段代码会编译成多条指令
(1)将对象new出来,给Singleton分配内存空间
(2)调用Singleton构造函数,初始化成员变量
(3)将singleton指向分配的内存,此时singleton就不为空了
java编译器容许指令乱序执行,因此二、3步执行顺序没法保证,就可能线程B执行了这段代码,singleton指向了内存空间,可是成员变量还没初始化完。此时若是线程A经过singleton== null进行判断,发现对象不为空,拿对象去使用,可是成员变量还没初始化完,就会出错。多线程

被volatile关键字修饰,可以保证内存可见性和禁止进行指令重排序,经过该特性保证了singleton = new Singleton()的指令执行顺序函数

  • 实现原理:经过synchronized和volatile实现线程安全。
  • 优势:缩小了synchronized关键字所修饰的范围
  • 缺点:volatile屏蔽掉了编译器必要的代码优化,因此在效率上会有影响

四、静态内部类

public class Singleton {
    private Singleton() {

    }

    private static class SingletonInner {
        private static final Singleton sInstance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInner.sInstance;
    }
}
复制代码
  • 实现原理:
    (1)如何保证线程安全:利用了Classloader的机制保证初始化时只有一个线程。虚拟机会保证静态类初始化的方法在多线程环境被正确加锁、同步,若是多个线程去初始化,只会有一个线程(线程A)去执行初始化操做,其余线程(线程B)须要阻塞等待。线程A执行完毕后,线程B唤醒后不会进入初始化的方法。同一个类加载器下,只会初始化一次
    (2)是否为延迟加载:内部类在被调用时才会被加载, 虽然Singleton被加载了,可是内部类不须要当即加载,因此Singleton尚未实例化,也算是懒汉式的一种(延迟加载),只有主动调用getInstance方法时Singleton才会被实例化。
  • 优势:延迟加载,而且没有线程同步问题,效率更高。

五、枚举

public enum Singleton{
    SINGLETON;
}
复制代码
  • 实现原理:对枚举进行反编译后,SINGLETON被声明为static的,虚拟机保证一个静态变量的线程安全问题,因此枚举是线程安全的。
  • 优势:《Effective Java》中提到,功能完整、使用简洁、无偿地提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然能够绝对防止屡次实例化等优势
    而其余单例方式若是禁止在反序列化时从新建立对象,须要在readResolve方法中,将sInstance返回。
private Object readResolve() throws ObjectStreamException {
    return sInstance;
}
复制代码

参考资料:优化

  • 《设计模式之禅》
  • 《Android源码设计模式解析与实战》
相关文章
相关标签/搜索