Hadoop之ShutdownHookManager源码分析

5 篇文章 0 订阅

  ShutdownHookManager的设计和jdk包中的设计基本类似,所以很有必要先分析一波jdk中的shutdownHook的源码,具体如下。
  在Runtime.java这个类中,有一个方法addShutdownHook,这个方法的作用就是允许用户注册一些JVM异常关闭的处理代码。先说明一下,JVM的关闭退出分为两类:
 1> 正常的退出,例如程序运行完毕正常的自动退出、程序主动调用了System.exit(int)方法;
 2> 用户强制关闭应用,例如手动执行kill pidkill -9 pid不会触发)或是ctrl+C这种方式而导致JVM关闭。
  而我们这里说的addShutdownHook方法,就是针对第二种关闭情况下,虚拟机进行的收尾操作,例如某个应用在执行过程中,产生了一些中间文件,执行着执行着,突然应用被强行kill了,此时假设调用过addShutdownHook注册了收尾的线程对这些中间文件执行清理操作,那么JVM会启动这个线程去清理这些临时文件然后退出。当然一个JVM运行过程中,其实是可以注册多个这类线程做不同类型的收尾善后工作的,查看addShutdownHook方法如下:

public void addShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        ApplicationShutdownHooks.add(hook);
    }

  这里的入参是一个Runnable的任务,其中可以设置线程优先级,对于JVM而言它在运行这些hook线程的时候,是遍历后依次启动,执行顺序是完全无法保障的。
  另外注意到addShutdownHook是通过调用ApplicationShutdownHooks.add(hook)方法来进行hook线程注册的,我们来看一下这个ApplicationShutdownHooks类的结构:

class ApplicationShutdownHooks {
    private static IdentityHashMap<Thread, Thread> hooks;
    static {
        try {
            Shutdown.add(1 /* shutdown hook invocation order */,
                false /* not registered if shutdown in progress */,
                new Runnable() {
                    public void run() {
                        runHooks();
                    }
                }
            );
            hooks = new IdentityHashMap<>();
        } catch (IllegalStateException e) {
            // application shutdown hooks cannot be added if
            // shutdown is in progress.
            hooks = null;
        }
    }
    
    private ApplicationShutdownHooks() {}
    
    static synchronized void add(Thread hook) {
        if(hooks == null)
            throw new IllegalStateException("Shutdown in progress");

        if (hook.isAlive())
            throw new IllegalArgumentException("Hook already running");

        if (hooks.containsKey(hook))
            throw new IllegalArgumentException("Hook previously registered");

        hooks.put(hook, hook);
    }
    
    static synchronized boolean remove(Thread hook) {
        if(hooks == null)
            throw new IllegalStateException("Shutdown in progress");

        if (hook == null)
            throw new NullPointerException();

        return hooks.remove(hook) != null;
    }
    
    static void runHooks() {
        Collection<Thread> threads;
        synchronized(ApplicationShutdownHooks.class) {
            threads = hooks.keySet();
            hooks = null;
        }

        for (Thread hook : threads) {
            hook.start();
        }
        for (Thread hook : threads) {
            try {
                hook.join();
            } catch (InterruptedException x) { }
        }

  注意这个类中的构造函数是私有化的,即外界是无法实例化这个对象的,而且它的所有方法均是静态的,那么这个类的作用很显然就是提供给用户进行hook线程注册的统一入口,本应用中所有需要注册hook线程的地方,均只能通过调用ApplicationShutdownHooks.add(hook);方法来进行注册。
  由ApplicationShutdownHooks的构造可以发现,在用户首次注册hook线程时,会调用静态代码块中的方法,到Shutdown类中去注册一个启动所有用户所注册的hook线程的父线程任务,还可以注意到Shutdown.add(int,boolean,Runnable)方法,其代码如下:

static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
        synchronized (lock) {
            if (hooks[slot] != null)
                throw new InternalError("Shutdown hook at slot " + slot + " already registered");

            if (!registerShutdownInProgress) {
                if (state > RUNNING)
                    throw new IllegalStateException("Shutdown in progress");
            } else {
                if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
                    throw new IllegalStateException("Shutdown in progress");
            }

            hooks[slot] = hook;
        }
    }

  这其中的hooks是一个线程数组:Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS]而很显然slot就是对应注册的启动hook线程的父线程在个数组中的位置,这个位置的左右就是在接下来启动这些线程的时候,启动顺序就是按照数组进行遍历。由于ApplicationShutdownHooks中注册的时候插入的数组中的位置slot=1,那么对应数组中的第一个位置是被谁占据了呢?对Shutdown.add()方法,通过用Intellij IDEA的find usage可以发现,另外一个调用这个方法的地方在System这个类中,进行查看可以发现是在System类初始化的时候就会调用一个setJavaLangAccess()的方法,在这个方法中会把Shutdown中的hooks数组中的第一个坑位给霸占了!
  另外,依据Runtime.java类中的备注来看,在使用addShutdownHook注册hook线程做善后工作时,还是需要注意尽量要避免线程内产生死锁,而且由于执行了exit方法后要尽量快的关闭应用,所以不要在这些线程中执行一些十分费时的操作(当然你如果要耍流氓好像也没啥办法吧?);还有就是,如果某个线程已经注册过,那么再次注册会报错,不过这里因为保存用户注册hook线程的是一个特殊的IdentityHashMap,这个map判断两个key是否相同的条件是key1=key2即直接判断两个对象的引用是否相同,意思就是对于同一个hook线程,不能连续两次调用注册,否则会报错。

  以上主要是jdk中的shutdownHook的主要逻辑,注意已经说过jdk中的逻辑完全无法保障线程的执行顺序,但是在hadoop中的ShutdownHookManager中,它在启动这些hook线程前,会对这个对应的hook线程数组进行排序,代码如下:

List<Runnable> getShutdownHooksInOrder() {
    List<HookEntry> list;
    synchronized (MGR.hooks) {
      list = new ArrayList<HookEntry>(MGR.hooks);
    }
    Collections.sort(list, new Comparator<HookEntry>() {

      //reversing comparison so highest priority hooks are first
      @Override
      public int compare(HookEntry o1, HookEntry o2) {
        return o2.priority - o1.priority;
      }
    });
    List<Runnable> ordered = new ArrayList<Runnable>();
    for (HookEntry entry: list) {
      ordered.add(entry.hook);
    }
    return ordered;
  }

  其中HookEntry为封装的Runnable任务,结构如下:

private static class HookEntry {
    Runnable hook;
    int priority;

    public HookEntry(Runnable hook, int priority) {
      this.hook = hook;
      this.priority = priority;
    }

    @Override
    public int hashCode() {
      return hook.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
      boolean eq = false;
      if (obj != null) {
        if (obj instanceof HookEntry) {
          eq = (hook == ((HookEntry)obj).hook);
        }
      }
      return eq;
    }
  }

  因此,在这里通过优先级先对数组进行排序,然后可以保证优先级高的数组优先启动,这样可以尽量保证优先级高的线程先一步执行完成(这个顺序肯定不是一定的,和系统调度有关),其它逻辑和jdk的实现基本一致,不再赘述。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值