单例模式5种实现方式

       在github上看到一个项目,实现了多种设计模式,就把它fork下来,一个一个看,然后也可以学习参考别人写的代码。
地址:https://github.com/iluwatar/java-design-patterns
(以下代码都转自上面的项目)
       单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

       实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
       
单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。
参考:wikipedia

常见的是

1.静态内部类(也叫饿汉模式)
2.懒汉模式
3.双重锁保证线程安全

还有另外3种写法:

4.序列化实现
5.枚举实现

1.静态内部类

//也叫饿汉模式,线程安全
public final class IvoryTower {

  /**
   * 静态变量属于类,不属于任何独立的对象,所以无需创建类的实例就可以访问静态变量。之所以会产生这样的结果,是因为编译器只为整个类创建了一个静态变量的副本,也就是只分配一个内存空间,虽然有多个实例,但这些实例共享该内存
   */
  private static final IvoryTower INSTANCE = new IvoryTower();

  /**
   * 私有化构造器,使得不能在外部调用构造器,也就不能在外部使用
   IvoryTower it =new IvoryTower();的方式创建对象,保证了实例化对象只有一个
   */
  private IvoryTower() {}

  /**
   * 用来返回实例化对象
   *
   * @return instance of the singleton.
   */
  public static IvoryTower getInstance() {
    return INSTANCE;
  }
}

2.懒汉模式
       和上面的不同之处在于没有在类加载的时候就创建对象,而是在真正要使用到这个对象的时候再去获取,优点在于如果这个创建对象过程很费时,那么一开始就创建会浪费较多时间,比如几分钟或者几个小时,万一创建好之后还用不到,那不是坑爹~~ 还有就是比如,我的类的构造函数中,有一些事可能需要依赖于别的类干的一些事(比如某个配置文件,或是某个被其它类创建的资源),我们希望他能在我第一次getInstance()时才被真正的创建。这样,我们可以控制真正的类创建的时刻,而不是把类的创建委托给了类装载器。于是有了懒加载。

public final class ThreadSafeLazyLoadedIvoryTower {

  private static ThreadSafeLazyLoadedIvoryTower instance;

  private ThreadSafeLazyLoadedIvoryTower() {}

  /**
   * 
   */
  public  static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() {

    if (instance == null) {   //(1)
                                //(2)
      instance = new ThreadSafeLazyLoadedIvoryTower();//(3)          
    }

    return instance;
  }
}

       注意getInstance()方法处的synchronized关键字,如果没有这个关键字,这个类就是线程不安全的。
懒汉模式在多线程环境下是不安全的,为什么这么说呢?假设现在有线程A,线程B,然后在线程A执行到代码(1)处之后,在代码(3)之前由于线程调度使得A暂停运行,一直停留在(2)处。这时候,如果线程B也执行到了代码(1)处的判断,由于线程A还尚未new对象就被暂停了,使得现在的instance还是null,所以B进行判断之后也会得到instance=null,然后也进入了代码(2),(3)处,顺利的new了一个对象。这时候如果线程A恢复运行,又会new一个对象,就导致了不止一个实例化对象。 但是如果在方法前加了一个synchronized关键字,在多线程环境下,它会使这个方法同时只被一个线程调用,那样就不存在线程A执行到一半被暂停转而被线程B执行,导致最后实例化了2个对象。
       但是思考一下,其实只需要在第一次实例化对象的时候才需要锁,在之后的调用的时候是不需要的,而上面这种写法使得每次调用都是加锁。优化方法就是双重锁,什么意思呢?就是对方法不加synchronized,但是在第一次判断instance==null,如果为null,再加锁,这样就使得加锁过程只会执行一次,一旦被执行过一次,instance!=null之后,就不会再次执行这个锁,就可以提高点效率!

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;
    if (result == null) {
      synchronized (ThreadSafeDoubleCheckLocking.class) {
        result = instance;
        if (result == null) {
          instance = result = new ThreadSafeDoubleCheckLocking();
        }
      }
    }
    return result;
  }
}

还有2种是序列化的方式和枚举方式
枚举方式:

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

       重写toString()只是为了测试的时候方便看到结果。枚举的方式确实简单明了。默认枚举实例的创建是线程安全的,所以不需要担心线程安全的问题。但是在枚举中的其他任何方法的线程安全由程序员自己负责。还有防止上面的通过反射机制调用私用构造器。
序列化的方式:

public final class InitializingOnDemandHolderIdiom implements Serializable {

  private static final long serialVersionUID = 1L;

  private InitializingOnDemandHolderIdiom() {}

  public static InitializingOnDemandHolderIdiom getInstance() {
    return HelperHolder.INSTANCE;
  }

  protected Object readResolve() {
    return getInstance();
  }

  private static class HelperHolder {
    public static final InitializingOnDemandHolderIdiom INSTANCE =
        new InitializingOnDemandHolderIdiom();
  }

}

       序列化是Java一个强大的功能,它可以将类的信息变成字节的形式存储在硬盘上或者通过socket传输。如果我们将一个单例类序列化,传输,再进行反序列化,那么如何能保证我们多次反序列化之后只会得到同一个实例化对象呢?
       readResolve()方法就是用来保证这一点的!而且这份实例代码里面用了一个静态内部类,然后在这个类里面初始化了一个静态对象。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值