美文网首页
设计模式1:单例模式

设计模式1:单例模式

作者: akak18183 | 来源:发表于2017-05-03 09:45 被阅读0次

在平时工作中,很多时候遇见的任务其实都有多种实现方法,或者说有不同的代码设计。
当然,就安卓开发而言,最重要的就是刚开始的架构。不过后来实现具体业务的代码同样可以追求更“优雅”一些。
何谓“优雅”?编程里面的优雅,无非就是在确保正确性、稳定性的前提下,做到:

  • 逻辑清晰,易于理解
  • 耦合度低,容易复用
  • 高效

实际上,说起来就这么一些,真正做起来可不容易。光正确性、稳定性就要花很大功夫,在这之外考虑一些东西,就是难上加难。
好在大部分问题别人都遇到并且解决过,而长久以来积累的一些编程的经验,就成为了设计模式(Design Patterns)。
所以说,为什么要用设计模式?因为用了设计模式之后代码会比较“优雅”。
当然,罗老师说的好,没有设计就是最好的设计。设计模式只是一种参考,不能生搬硬套。否则还真不如没有。
今天就来学一学最简单的——单例模式(Singleton)。

单例模式

基本概念

这个模式的意图是希望对象只创建一个实例,并且提供一个全局的访问点。
为什么要用单例模式?单例模式的对面自然是多例,为什么不能有多个实例呢?
答案很明显,一是节约资源,比如SimpleDateFormat,到处创建的话,效率肯定不如单个的好(当然这个类线程不安全,复用的时候需要注意);二是方便管理,比如DB的Manager类,1个DB只需要1个Manager,多了的话反而会变得混乱。
同样的,单例模式的局限性也很明显:

  • 全局可见,可能要考虑线程安全性;
  • 全局调用,因此难以释放;
  • 继承基本上没用;
  • 耦合度高。

创建方法

就大体而言,我把单例模式的创建方法分成两种:

1.预先初始化(Eager Initialization)

预先初始化的意思就是,只要有机会就创建实例。这个方法是线程安全的(因为没有带入更多逻辑,这里说的线程安全都是对创建单例而言)。

2. 延迟初始化(Lazy Initialization)

和1相反,是在最后时刻也就是马上要用的时候来创建实例。这个不是天生线程安全的,需要做一些处理。处理又有不同的处理方法,具体再说。

代码

废话不多说,上代码了(出处参考这里):

1. 预先初始化
/**
 * Singleton class. Eagerly initialized static instance guarantees thread safety.
 */
public final class IvoryTower {

  /**
   * Private constructor so nobody can instantiate the class.
   */
  private IvoryTower() {}

  /**
   * Static to class instance of the class.
   */
  private static final IvoryTower INSTANCE = new IvoryTower();

  /**
   * To be called by user to obtain instance of the class.
   *
   * @return instance of the singleton.
   */
  public static IvoryTower getInstance() {
    return INSTANCE;
  }
}

这个类就是最简单的预先加载单例模式。私有构造方法从而不会被外界调用,私有静态实例创建,因此叫做“预加载”。同时final修饰,不可变。
这个方法是线程安全的,因为实例在被调用之前已经生成,而且不可变。缺点自然是有浪费资源的嫌疑,因为没被调用时已经占用了内存。
适用场景:

  • 需要经常使用;
  • 不会占用太多资源。
2.基于枚举的预先初始化
/**
 * Enum based singleton implementation. Effective Java 2nd Edition (Joshua Bloch) p. 18
 */
public enum EnumIvoryTower {

  INSTANCE;

  @Override
  public String toString() {
    return getDeclaringClass().getCanonicalName() + "@" + hashCode();
  }
}

使用时调用EnumIvoryTower.INSTANCE。
看上去更加简单,也线程安全,原理和1类似。不过需要注意的是扩展会受到限制:继承、序列化等等。

3. 线程安全的延迟初始化
/**
 * Thread-safe Singleton class. The instance is lazily initialized and thus needs synchronization
 * mechanism.
 *
 * Note: if created by reflection then a singleton will not be created but multiple options in the
 * same classloader
 */
public final class ThreadSafeLazyLoadedIvoryTower {

  private static ThreadSafeLazyLoadedIvoryTower instance;

  private ThreadSafeLazyLoadedIvoryTower() {}

  /**
   * The instance gets created only when it is called for first time. Lazy-loading
   */
  public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() {

    if (instance == null) {
      instance = new ThreadSafeLazyLoadedIvoryTower();
    }

    return instance;
  }
}

这个方法就是在被调用实例的时候去创建一个,而为了线程安全,使用synchronized 来修饰getInstance方法。好处就是资源分配更合理,要用了才占资源;坏处就是速度慢了,创建可能会慢,而且多个线程一起访问的时候还有锁。

4. 线程安全的双重检验锁延迟初始化
/**
 * Double check locking
 * <p/>
 * http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
 * <p/>
 * Broken under Java 1.4.
 *
 * @author mortezaadi@gmail.com
 */
public final class ThreadSafeDoubleCheckLocking {

  private static volatile ThreadSafeDoubleCheckLocking instance;

  /**
   * private constructor to prevent client from instantiating.
   */
  private ThreadSafeDoubleCheckLocking() {
    // to prevent instantiating by Reflection call
    if (instance != null) {
      throw new IllegalStateException("Already initialized.");
    }
  }

  /**
   * Public accessor.
   *
   * @return an instance of the class.
   */
  public static ThreadSafeDoubleCheckLocking getInstance() {
    // local variable increases performance by 25 percent
    // Joshua Bloch "Effective Java, Second Edition", p. 283-284
    
    ThreadSafeDoubleCheckLocking result = instance;
    // Check if singleton instance is initialized. If it is initialized then we can return the instance.
    if (result == null) {
      // It is not initialized but we cannot be sure because some other thread might have initialized it
      // in the meanwhile. So to make sure we need to lock on an object to get mutual exclusion.
      synchronized (ThreadSafeDoubleCheckLocking.class) {
        // Again assign the instance to local variable to check if it was initialized by some other thread
        // while current thread was blocked to enter the locked zone. If it was initialized then we can 
        // return the previously created instance just like the previous null check.
        result = instance;
        if (result == null) {
          // The instance is still not initialized so we can safely (no other thread can enter this zone)
          // create an instance and make it our singleton instance.
          instance = result = new ThreadSafeDoubleCheckLocking();
        }
      }
    }
    return result;
  }
}

这个是3的加强版,提高了效率,但是代价就是代码的复杂度。Java 1.4以下不可用。
其实很精妙,instance用volatile修饰,虽然是静态变量但刚开始并没有初始化。Volatile的意思就是不会被重排序,读取的总是最后写入的值。当然光靠Volatile并不能解决线程安全问题,因为可能两个线程同时读之后再写。因此后面还有一个synchronized (ThreadSafeDoubleCheckLocking.class),直接锁类。
假设两个线程AB先后尝试获取instance,假如线程不安全,后果就是创建两遍instance。在这里,A肯定不会创两遍,主要看B,B进来的时候假如A已经完事了自然好说,假设A还在创建当中,B一看是null,准备去创建,发现门被锁上了(synchronized),等到门开了的时候,A已经出来了,也在里面创建了实例,B再看的时候就不是null了。
4比3快,因为只同步块而不是方法。

5. InitializingOnDemandHolderIdiom(实在不知道怎么翻译)
/**
 * The Initialize-on-demand-holder idiom is a secure way of creating a lazy initialized singleton
 * object in Java.
 * <p>
 * The technique is as lazy as possible and works in all known versions of Java. It takes advantage
 * of language guarantees about class initialization, and will therefore work correctly in all
 * Java-compliant compilers and virtual machines.
 * <p>
 * The inner class is referenced no earlier (and therefore loaded no earlier by the class loader) than
 * the moment that getInstance() is called. Thus, this solution is thread-safe without requiring special
 * language constructs (i.e. volatile or synchronized).
 *
 */
public final class InitializingOnDemandHolderIdiom {

  /**
   * Private constructor.
   */
  private InitializingOnDemandHolderIdiom() {}

  /**
   * @return Singleton instance
   */
  public static InitializingOnDemandHolderIdiom getInstance() {
    return HelperHolder.INSTANCE;
  }

  /**
   * Provides the lazy-loaded Singleton instance.
   */
  private static class HelperHolder {
    private static final InitializingOnDemandHolderIdiom INSTANCE =
        new InitializingOnDemandHolderIdiom();
  }
}

这个方法使用了静态内部类来获得实例,原理就是内部类在被调用之前不会创建实例。代码量少,不需要手动同步。

总结

不要看单例模式看起来简单,大神们的实现都是直指Java核心,编译原理,线程安全,都考虑到了。这5种方法就是大浪淘沙,无数程序员智慧的结晶。具体使用哪种方法得看需求。

相关文章

  • 单例模式Java篇

    单例设计模式- 饿汉式 单例设计模式 - 懒汉式 单例设计模式 - 懒汉式 - 多线程并发 单例设计模式 - 懒汉...

  • 设计模式

    设计模式简介 单例设计模式 问题:单例设计模式是什么?为什么要学它?怎么用它? 1.定义:单例模式(Singlet...

  • 设计模式第二篇、单例设计模式

    目录1、什么是单例设计模式2、单例设计模式的简单实现3、单例设计模式面临的两个问题及其完整实现4、单例设计模式的应...

  • python中OOP的单例

    目录 单例设计模式 __new__ 方法 Python 中的单例 01. 单例设计模式 设计模式设计模式 是 前人...

  • 单例

    目标 单例设计模式 __new__ 方法 Python 中的单例 01. 单例设计模式 设计模式设计模式 是 前人...

  • 设计模式 - 单例模式

    设计模式 - 单例模式 什么是单例模式 单例模式属于创建型模式,是设计模式中比较简单的模式。在单例模式中,单一的类...

  • python之理解单例模式

    python之理解单例模式 1、单例模式 单例模式(Singleton Pattern)是一种常见的软件设计模式,...

  • 设计模式学习之单例模式

    设计模式之单例模式 1 什么是单例模式 在维基百科中单例模式定义为 单例模式,也叫单子模式,是一种常用的软件设计模...

  • 设计模式

    常用的设计模式有,单例设计模式、观察者设计模式、工厂设计模式、装饰设计模式、代理设计模式,模板设计模式等等。 单例...

  • iOS模式设计之--创建型:1、单例模式

    iOS模式设计之--1、单例模式

网友评论

      本文标题:设计模式1:单例模式

      本文链接:https://www.haomeiwen.com/subject/bwqktxtx.html