Android中的单例模式

来源:互联网 发布:制造业大数据采集 编辑:程序博客网 时间:2024/05/16 15:23

单例模式在设计模式中是最方便人们理解的。虽然容易理解,但里面坑挺多的,所以在面试题中经常哪来做考题。简单来说,单例模式,就是为了保证类对象只有并且只能创建一个。规模比较大的类,或者需要反复使用的类,才会使用单例模式。下面一起探讨一下单例模式的几种方式。

懒汉式,线程不安全

当提到单例模式的时候,可能很多人会在第一时间想到如下的代码。
    public class Singleton {        private static Singleton instance;        private Singleton (){}        public static Singleton getInstance() {         if (instance == null) {             instance = new Singleton();         }         return instance;        }    }
在这个方法中,其实代码非常的简单明了,但是其存在的问题就是,当多个线程并行调用上面的getInstance()时,就会创建多个实例,结果就是不能在多线程    下正常运行。

懒汉式,线程安全

    为了解决上述的问题,其实很简单,只需要在getInstance()加一个同步锁(synchronized)就可以了.
public static synchronized Singleton getInstance() {         if (instance == null) {             instance = new Singleton();         }         return instance;        }
    如果多个线程进入,只允许进入一个,且执行完之后,才能允许另一个线程进入。

双重检验锁

    双重检验锁,是一种使用同步块加锁的方法。因为会有两次if判断检查instance是否为空,一次是在同步块外,一次是在同步块内。
    public static Singleton getSingleton() {        if (instance == null) {                                     synchronized (Singleton.class) {                if (instance == null) {                                    instance = new Singleton();                }            }        }        return instance ;    }
    这段代码看起来很完美,不过有问题,主要是instance = newSingleton()这句,这并非一个原子操作,事实上在JVM中这句话大概做了下面三件事情。
    1.给instance分配内存
    2.调用Singleton的构造函数来初始化成员变量
    3.将instance对象指向分配的内存空间(执行完这一步instance就非null了)
    但是在JVM的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三部的顺序是不能保证的,最终的执行顺序可能是1-2-3,也可能是1-3-2.如果是后者,则在3执行完毕、2未执行之前,被线程二抢占了。这时instance已经是非null了(但却没有初始化),所以线程二会直接返回instance,然后使用,然后就GG报错了。
 我们只需要把instance变量声明成volatile就可以了。
         
    public class Singleton {        private volatile static Singleton instance; //声明成 volatile        private Singleton (){}        public static Singleton getSingleton() {            if (instance == null) {                                         synchronized (Singleton.class) {                    if (instance == null) {                               instance = new Singleton();                    }                }            }            return instance;        }       }
在多线程并发中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见
性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。它在某些情况下比synchronized的开销更小。

饿汉式 static final field

这种方法很简单,因为单利的实例被声明称static和final变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。  
    public class Singleton{        //类加载时就初始化        private static final Singleton instance = new Singleton();            private Singleton(){}        public static Singleton getInstance(){            return instance;        }    }
这种写法缺点是它不是一种懒汉式加载模式,单例会在加载类后一开始就被初始化,即时客户端没有调用getInstance()方法,饿汉式的创建方式在
一些场景中无法使用:譬如Singleton实例的创建时依赖参数或者配置文件的,在getInstance()之前必须调用某个方法设置参数给它,那样这种单例写
就无法使用了。

静态内部类

个人倾向于静态内部类的方法。
    public class Singleton {          private static class SingletonHolder {              private static final Singleton INSTANCE = new Singleton();          }          private Singleton (){}          public static final Singleton getInstance() {              return SingletonHolder.INSTANCE;         }      }
这种写法仍然使用JVM本身机制保证了线程安全问题;由于SingletonHolder是私有的,除了getInstance之外没有办法访问它,因为它是懒汉式的;
同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖JDK版本。

枚举

这种方法很简单,也是它最大的优点了。
    public enum EasySingleton{        INSTANCE;    }
通过EasySingleton.INSTANCE来访问实例,比调用getInsance()方法方便多了。创建枚举默认线程是安全的,所以不必担心double checked locki
-ng而且还能防止反序列化导致重新创建新的对象。这种方法很少看到有人写,大概是因为不熟悉。

总结

一般来说,单例模式一般分为5种写法:懒汉式、饿汉式、双重检验锁、静态内部类、枚举。上述所说多事线程安全的实现,开头中的第一种方法不
正确的写法。正常情况下没直接使用饿汉式就好了,如果明确要求要懒汉式会倾向于静态内部类,如果涉及到反序列化创建对象时会试着使用枚举方
来实现单例。




1 0
原创粉丝点击