Android中的单例设计模式

1.设计模式实例-单例模式

单例模式,故名思议,是指在一个类中通过设置静态使得其仅创造一个唯一的实例。这样设置的目的是满足开发者的希望??这个类只需要被实例化创建一次,同时因为其为静态的缘故,加载的速度也应该快于正常实例化一个类的速度(理论上)。

在Android开发中,当我们需要创建一个Fragment的时候常常会用到这样的模式,没有代码的学习是虚无的,接下来亮代码学习:

class SelectFrame extends Fragment {

    private final static String selectFrameKey = "SFKey";
    private static SelectFrame mSelectFrame;
    private ArrayList<String> frameList;
    public static SelectFrame getInstance(ArrayList<String> frameList){
        if (mSelectFrame == null) {
            mSelectFrame = new SelectFrame();
        }
        Bundle bundle = new Bundle();
        bundle.putStringArrayList(selectFrameKey, frameList);
        mSelectFrame.setArguments(bundle);
        return mSelectFrame;
    }
......


这是我在一个Fragment类里面定义的其中一部分,首先必须要定义一个自身的静态mSelectFrame。然后通过一个静态方法 getInstance()来实例化这个mSelectFrame,很明显,这里通过判断其是否为空的方式,使得每一次我们使用getInstance的时候都返回的是同一个对象mSelectFrame。

getInstance方法中有时我们也会放入一些我们需要传递的参数,比如我这个方法中放入了一个List对象,然后直接在里面用Bundle装载这个List对象,原本我们可能是在外部来做这些操作的,然而现在却直接通过这个方法将数据传入的操作集成到了这个Fragment中,即减少了外部Activity的逻辑代码,也使得这个Fragment在复用的时候操作更方便。(再通过setArguments的方法保存数据)。

接下来看我们的onCreateView方法:

  @Override
  public View onCreateView(LayoutInflater inflater,
      @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    view = inflater.inflate(R.layout.select_frame_fragment, container, false);
    Bundle bundle = mSelectFrame.getArguments();
    frameList = bundle.getStringArrayList(selectFrameKey);
}

这里我们就将从前面getInstance中setArguments保存下来的数据通过Fragment的getArguments方法重新将数据读取出来。这样就完成了我们的目的。

接下来就是在Activity中创建我们这个Fragment对象了:

    selectFrameFrag = SelectFrame.getInstance(nameFrameThumbnail);
    fm.beginTransaction().add(R.id.fragment_frame_container, selectFrameFrag).commit();

(fm是FragmentManager..)

这样就OK了。

2.单例模式的分类

单例模式有两种创建方式,分别为饿汉式和懒汉式。

饿汉式,故名思议,很饿,在一开始就直接创建了这个对象。

懒汉式,顾名思义,很懒,只有在你要调用它的时候,通过自己写的方法里面来对他实例化。刚刚上面那个例子就是懒汉式的。

也许这么说你会有一点不明白(其实应该没有人不明白吧= =),然后其实只要看代码的这个地方:

  private static SelectFrame mSelectFrame;

上面这个代码,这里一开始定义自身的静态时,没有实例化它,那么就是个懒汉.然后只有在我调用getInstance方法的时候,才在里面对他进行实例化(new SelectFrame()).

然后我们再看看饿汉:

private static SelectFrame mSelectFrame = new SelectFrame();

在定义的时候就已经实例化了.

3.单例模式的线程安全问题

没错,单例模式毕竟就是像上面讲的这么简单。但需要注意的是,单例模式中的饿汉式是线程安全的,而懒汉式是线程不安全的,我们一般会再懒汉式的getInstance方法中通过synchronized上锁。类似于这样:

    if (mSelectFrame == null) {
      synchronized (SelectFrame.class) {
        if (mSelectFrame == null) {
          mSelectFrame = new SelectFrame();
        }
      }
    }
有人说为什么要用两个if XX == null。这里是因为提高效率,只有一个也是可以的,但效率上大大减低,因为不是每一次调用的时候我们要判断他是否同步,应该是如果判断当前为null的话,我们就直接不让他进入了,不需要判断是否同步。只有满足第一个条件的时候,我们才需要用锁来判断它是否线程安全。(这里的意思也是说明if一条语句的判断速度当然比synchronized (SelectFrame.class)快。。)

另外,在我们使用单例模式的时候,有些人会去私有化这个类的构造方法,使得这个类只能通过自己写的getInstance()来创建。类似于这样:

  private SelectFrame() {
    // 并不需要做什么,只需要将这个外面的public改成private就好了=。=
  }

嗯嗯,这么一来外部就只能按我要求的方法来创建这个对象了.

在这里也基本将单例模式的使用基本讲完了,接下来还会继续写其他的设计模式的使用,如果有涉及到Android上的都会尽力用Android上的例子来讲,加深印象。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

单例模式是最常用到的设计模式之一,熟悉设计模式的朋友对单例模式都不会陌生。一般介绍单例模式的书籍都会提到 饿汉式和 懒汉式这两种实现方式。但是除了这两种方式,本文还会介绍其他几种实现单例的方式,让我们来一起看看吧。

原文链接: http://tianweili.github.io/blog/2015/03/02/singleton-pattern/

简介

单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。

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

基本的实现思路

单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)。

单例的实现主要是通过以下两个步骤:

  1. 将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
  2. 在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。

注意事项

单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。

单例模式的八种写法

1、饿汉式(静态常量)[可用]

 
  1. public class Singleton {

  2.  
  3. private final static Singleton INSTANCE = new Singleton();

  4.  
  5. private Singleton(){}

  6.  
  7. public static Singleton getInstance(){

  8. return INSTANCE;

  9. }

  10. }

  11.  

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

2、饿汉式(静态代码块)[可用]

 
  1. public class Singleton {

  2.  
  3. private static Singleton instance;

  4.  
  5. static {

  6. instance = new Singleton();

  7. }

  8.  
  9. private Singleton() {}

  10.  
  11. public Singleton getInstance() {

  12. return instance;

  13. }

  14. }

  15.  

这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

3、懒汉式(线程不安全)[不可用]

 
  1. public class Singleton {

  2.  
  3. private static Singleton singleton;

  4.  
  5. private Singleton() {}

  6.  
  7. public static Singleton getInstance() {

  8. if (singleton == null) {

  9. singleton = new Singleton();

  10. }

  11. return singleton;

  12. }

  13. }

  14.  

这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

4、懒汉式(线程安全,同步方法)[不推荐用]

 
  1. public class Singleton {

  2.  
  3. private static Singleton singleton;

  4.  
  5. private Singleton() {}

  6.  
  7. public static synchronized Singleton getInstance() {

  8. if (singleton == null) {

  9. singleton = new Singleton();

  10. }

  11. return singleton;

  12. }

  13. }

  14.  

解决上面第三种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。

缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。

5、懒汉式(线程安全,同步代码块)[不可用]

 
  1. public class Singleton {

  2.  
  3. private static Singleton singleton;

  4.  
  5. private Singleton() {}

  6.  
  7. public static Singleton getInstance() {

  8. if (singleton == null) {

  9. synchronized (Singleton.class) {

  10. singleton = new Singleton();

  11. }

  12. }

  13. return singleton;

  14. }

  15. }

  16.  

由于第四种实现方式同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块。但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。

6、双重检查[推荐用]

 
  1. public class Singleton {

  2.  
  3. private static volatile Singleton singleton;

  4.  
  5. private Singleton() {}

  6.  
  7. public static Singleton getInstance() {

  8. if (singleton == null) {

  9. synchronized (Singleton.class) {

  10. if (singleton == null) {

  11. singleton = new Singleton();

  12. }

  13. }

  14. }

  15. return singleton;

  16. }

  17. }

  18.  

Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。

优点:线程安全;延迟加载;效率较高。

7、静态内部类[推荐用]

 
  1. public class Singleton {

  2.  
  3. private Singleton() {}

  4.  
  5. private static class SingletonInstance {

  6. private static final Singleton INSTANCE = new Singleton();

  7. }

  8.  
  9. public static Singleton getInstance() {

  10. return SingletonInstance.INSTANCE;

  11. }

  12. }

  13.  

这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。

类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

优点:避免了线程不安全,延迟加载,效率高。

8、枚举[推荐用]

 
  1. public enum Singleton {

  2. INSTANCE;

  3. public void whateverMethod() {

  4.  
  5. }

  6. }

借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过。

优点

系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。

缺点

当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。

适用场合

  • 需要频繁的进行创建和销毁的对象;
  • 创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
  • 工具类对象;
  • 频繁访问数据库或文件的对象。

猜你喜欢

转载自blog.csdn.net/qq_42618969/article/details/81120809