vlambda博客
学习文章列表

从HDFS中学习单例模式

在平时工作的时候,各种优秀的开源组件中的代码风格、架构设计、奇技淫巧等是值得我们深入学习的。这样当我们要从0开始设计一个大的系统时,就可以参照这些组件的实践经验来快速开发。但在这之前我们有针对性地对这些良好的实践进行积累。

举个例子,假设我们要开发一个系统,它需要有监控模块,这块就可以参考Hadoop、Alluxio等开源组件,看它们的监控模块是怎么实现的。当然不同的组件监控模块的实现不一样,需要根据实际情况进行取舍。

那本文我们就来通过HDFS学习使用静态内部类实现单例模式的实践:

一、经典的实现

代码如下:

package com.zhb;
public class Singleton {

    /** 私有化构造器 */
    private Singleton() {
    }

    /** 对外提供public的获取实例的方法 */
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    /** 写一个静态内部类,里面实例化外部类 */
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}

有几个要点:

① 这种情况是懒加载的。只有调用到getInstance方法的时候,才会new Singleton。为什么?因为JVM规定了类初始化的时机,没有规定类初始化时同时初始化内部类。访问类的静态成员的时候才会去初始化类。

②是单例的,为什么?因为INSTANCE是个静态常量,只会被初始化一次。

③线程安全的,为什么?类加载的过程是线程安全的,这是JVM做的保证。JVM内部会保证一个类的 方法在多线程环境下被正确的加锁同步,如果多个线程同时去进行“类的初始化”,那么只有一个线程会去执行类的 方法,其他的线程都要阻塞等待,直到这个线程执行完 方法。然后执行完 方法后,其他线程唤醒,但是不会再进入 ()方法。也就是说同一个加载器下,一个类型只会初始化一次。

二、HDFS中用静态内部类实现单例设计模式

为方便理解,大家可以忽略掉一些HDFS中的专有名词,可以仅仅把它当成一个类。

在DFSClient类中getLeaseRenewer()方法用于获取“lease renewer”的实例。

  /** Return the lease renewer instance. The renewer thread won't start
   *  until the first output stream is created. The same instance will
   *  be returned until all output streams are closed.
   */

  public LeaseRenewer getLeaseRenewer() {
    return LeaseRenewer.getInstance(
        namenodeUri != null ? namenodeUri.getAuthority() : "null", ugi, this);
  }

LeaseRenewer的构造方法是private的,所以外部无法直接new LeaseRenewer对象。只能通过LeaseRenewer.getInstance方法获得LeaseRenewer对象。

继续看getInstance()方法。

  /** Get a {@link LeaseRenewer} instance */
  public static LeaseRenewer getInstance(final String authority,
      final UserGroupInformation ugi, final DFSClient dfsc)
 
{
    // Factory是LeaseRenewer类的静态内部类。
    final LeaseRenewer r = Factory.INSTANCE.get(authority, ugi);
    r.addClient(dfsc);
    return r;
  }

重点在Factory.INSTANCE.get()方法,这里与经典的单例不同,做了些小修改,不过原理是一样的。Factory.INSTANCE也是单例的,只不过这个INSTANCE是Fatory对象,之后可以用它调用静态内部类Factory里面的方法进行一些逻辑的处理。因为INSTANCE是单例的,所以外部或得到的都是同一个Factory对象,调用Factory对象的get方法也就是同一个get方法。这里get方法又通过synchronized修饰,同一时刻只有一个线程可以执行。在get方法里根据参数从renewers这个Map中获取对应的LeaseRenewer对象。

最后把代码整合一下,方便理解:

public static LeaseRenewer getInstance(final String authority,
      final UserGroupInformation ugi, final DFSClient dfsc)
 
{
    // Factory是LeaseRenewer类的静态内部类。
    final LeaseRenewer r = Factory.INSTANCE.get(authority, ugi);
    r.addClient(dfsc);
    return r;
  }

public class LeaseRenewer {

    /**
   * A factory for sharing {@link LeaseRenewer} objects
   * among {@link DFSClient} instances
   * so that there is only one renewer per authority per user.
   */

  private static class Factory {

    private static final Factory INSTANCE = new Factory();

    /** Get a renewer. */
    private synchronized LeaseRenewer get(final String authority,
        final UserGroupInformation ugi)
 
{
      final Key k = new Key(authority, ugi);
      LeaseRenewer r = renewers.get(k);
      if (r == null) {
        r = new LeaseRenewer(k); // 静态内部类new外部类。
        renewers.put(k, r);
      }
      return r;
    }
   //省略其他。。。
  }
}