概述
定义:确保某个类只有一个实例,而且自行实例化提供给外部使用。
使用场景:某个类型的对象只应该有且只有一个,或者避免创建多个对象消耗过多的资源时。如:访问IO或数据库时要考虑单例模式。
N种实现方式及比较
饿汉式
public class SingleTon {
//将构造函数私有化
private SingleTon() {
}
//创建私有实例对象
private static final SingleTon singleTonInstance = new SingleTon();
//对外提供方法,返回实例对象
public static SingleTon getInstance() {
return singleTonInstance;
}
}
优点:饿汉式的好处是线程安全,因为虚拟机保证只会装载一次,再装载类的时候,是不会并发的,这样就保证了线程安全的问题。
缺点:实例对象是static的,在声明的时候就实例化了,浪费资源。
懒汉式
public class SingleTon {
//声明私有化
private static SingleTon singleTonInstance;
//将构造函数私有化
private SingleTon() {
}
//懒汉式
private static synchronized SingleTon getInstance(){
if (null==singleTonInstance){
singleTonInstance = new SingleTon();
}
return singleTonInstance;
}
}
优点:用到的时候才会去实例化,在一定程度上节约了资源。
缺点:getInstance方法是用synchronized修饰的,该方法是同步的,为了保证线程安全,但是导致每次调用该方法的时候都会被同步,这样会消耗不必要的资源(不必要的同步开销)。所以这种模式一般不建议使用。
Double Check Lock(DCL模式):双重检查锁定
public class SingleTon {
//声明私有化
private static SingleTon singleTonInstance;
//将构造函数私有化
private SingleTon() {
}
//Double Check Lock
public static SingleTon getInstance(){
if (singleTonInstance==null){
synchronized (SingleTon.class){
if (singleTonInstance==null){
singleTonInstance = new SingleTon();
}
}
}
return singleTonInstance;
}
}
可以看到getInstance()方法对singleTonInstance进行两次判空,对懒汉式进行了优化,只有在第一次实例化的时候才会走第二个分支,才会同步,避免了每次都同步造成的不必要的资源消耗。
优点:第一次执行getInstance方法时才会实例化,资源利用率高,效率高。
缺点:偶尔失效(高并发条件下,由于JDK版本问题,在jdk1.5之前会失败)。
静态内部类实现
public class SingleTon {
//将构造函数私有化
private SingleTon() {
}
public static SingleTon getInstance() {
return SingleTonHoulder.singleTonInstance;
}
//静态内部类
public static class SingleTonHoulder {
private static final SingleTon singleTonInstance = new SingleTon();
}
}
第一次调用getInstance()方法的时候,虚拟机才会加载SingleTonHoulder静态内部类
优点:线程安全,保证单例的唯一性,延迟了对象的实例化,是推荐的方式。
缺点:传参不方便。
那么这个时候,问题来了,参数怎么传递?这个确实没懒汉式方便,不过没关系,我们可以定义一个init()就可以了,只不过初始化的时候多了一行代码;
public class SingleTon {
//将构造函数私有化
private SingleTon() {
}
public static SingleTon getInstance() {
return SingleTonHoulder.singleTonInstance;
}
//静态内部类
public static class SingleTonHoulder {
private static final SingleTon singleTonInstance = new SingleTon();
}
private Context mContext;
public void init(Context context){
this.mContext = context;
}
}
初始化:
SingleTon mSingleTon = SingleTon.getInstance();
mSingleTon.init(this);
枚举
以上的单例实现方法都没有考虑一个因素:反序列化,即使构造函数是私有的,反序列化仍然有特殊的途径去创建类的一个新的实例。但是同构枚举实现单例不会有这样的问题,因为枚举提供了序列化机制。
public class SingleTon {
public static SingleMode getInstance(){
return Singleton.SINGLETON.getSingleTon();
}
public enum Singleton{
SINGLETON ; //枚举本身序列化之后返回的实例,名字随便取
private AppUninstallModel singleton;
Singleton(){ //JVM保证只实例一次
singleton = new AppUninstallModel();
}
// 公布对外方法
public SingleMode getSingleTon(){
return singleton;
}
}
}
好吧,这样就ok了,但还是那个问题,初始化参数跟静态类一样,还是得重新写个 init() 有失必有得吧。
使用容器实现单例
public class SingleTonManager {
private static Map<String, Object> objMap = new HashMap<>();
private SingleTonManager() {
}
public static void registerService(String key, Object object) {
if (!objMap.containsKey(key)) {
objMap.put(key, object);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}
将多种单例类型注入到一个统一的管理类中,使用时根据key获取对应的对象,这种模式使得我们可以和管理多种类型的单例,并且在使用的时候可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏的具体的实现,降低了耦合度。
Android源码中单例使用
LayoutInflater.from(Context context);
EventBus.getDefault()
ImageLoader.getInstance();
......
他们实现单例的方式不同,大家可以去看源码
总结
单例模式是使用频率较高的设计模式,但是由于客户端通常没有高并发的情款,选择哪种实现方式并不会有太大影响。但是出于效率考虑,推荐使用“DCL”和“静态内部类”实现方式。
单例模式的优点:
1.在内存中只有一个实例,减少内存开支
2.只生产一个实例,减少系统性能的性能开销
3.避免对资源的多重占用。
4.可以在系统设置全局的访问点,优化和共享资源访问。(例如可以设置一个单例类,负责所有数据表的映射处理)
单例的缺点;
1.单例一般没有接口,扩展很困难。
2.单例如果持有Context对象,很容易引起内存泄漏,最好传递全局的Application Context。