对一些代码按自己的理解修改

在JAVA环境中,一个任务一般是由一个独立线程来引导实现的,如果在执行过程中,某一个线程发生异常(产生的原因很多,比如软件升级、运行环境改变、系统资抢占等),那么该线程就会停止运行,直到下次任务重新被提交。对于实时环境来说当前任务是失败的。我们无法预测和完全避免异常的发生,但是可以通过一些技术手段来跟踪任务的状态,从而及时发现问题并恢复正常,减少损失。

一个简单的例子:

A任务每秒执行一个简单的代数运算 j = 1/ i,并打印结果。我们故意在其中设置了一个异常陷阱,使得执行过程中出现一次被0除的算术异常,下面结合这个例子讲述监控原理。

public class ATask extends Thread {
    /**
     * A任务定期修改自己的sign标志,sign是一个布尔变量,理论上只要A没有死,
     * 那么sign肯定是周期变化的(和心跳概念差不多)
     */
    public boolean sign = false;
    Stakeout m;

    ATask(Stakeout m) {
        m = m;
        start();
    }

    public void run() {
        try {
            for (int i = -3; i <= 5; i++) {
                int j = 1 / i;   // 人为设置过程中陷阱
                sign = !sign;  // 活动状态
                System.out.println("i=" + i + ": status=" + sign);
                try {
                    sleep(2000);
                } catch (InterruptedException ie) {
                    System.out.println("A is Interrupted!");
                }
            }
            m.Keepchecking = false;  //A 正常结束后关闭监控线程
            System.out.println("A is Ending M");
        } catch (Exception e) {
            System.out.println("A become Exception!");
        }
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("线程:"+Thread.currentThread()+" is over by gc~~~~~~~~~~~~~");
    }
}

为了适应监控,在A任务中相应增加一些可以被监控的状态和行为:
sign状态标志
sign= !sign; 改变状态
类似于心跳,如果监控方遇到sign都是同一个说明,A出现异常了。

为了监控A,我们设计了一个监控线程Stakeout。Stakeout中定义了一些关键逻辑变量:
Keepchecking 持续监控标志
laststatus 保存上次监控状态
maydeadtimes 监控线程可能死亡的计数器
maydeadtimeout 定义判断线程死亡的边界条件
deadtimes 监控线程死亡次数的计数器
deadtimeout 定义判断线程不正常的边界条件

/**
 * 监控线程
 */
public class Stakeout extends Thread {
    public static boolean Keepchecking = true;  // 持续监控标志
    boolean laststatus;     //保存上次监控状态
    int maydeadtimes = 0;  //监控线程可能死亡的计数器
    int maydeadtimeout = 3;//定义判断线程死亡的边界条件
    int deadtimes = 0;     //监控线程死亡次数的计数器
    int deadtimeout = 3;   //定义判断线程不正常的边界条件
    ATask a;

    Stakeout() {
        start();
    }

    public void run() {
        schedule();
        while (Keepchecking) {
            laststatus = a.sign;
            try {
                sleep(2000);
            } catch (InterruptedException e) {
                System.out.println("M is Interrupted!");
            }
            System.out.println("M read A status = " + a.sign);
            //如果过了一段时间记录的状态与a中的状态相同,说明可能存在线程死亡的情况
            if (laststatus == a.sign) {
                // 线程可能死亡的次数超过设定值
                if (++maydeadtimes >= maydeadtimeout) {
                    //实际线程死亡的次数
                    if (++deadtimes >= deadtimeout) {
                        System.out.println("Alert! A is unstable, M will stop it");
                        a = null;
                        break;
                    } else {
                        System.out.println("A is deaded!");
                        schedule();
                        System.out.println("M is restarting A!\n____________________________\n");
                    }
                }
            } else {
                maydeadtimes = 0;
            }
        }
    }

    /**
     * 初始化任务
     */
    private void schedule() {
       a = new ATask(this);
    }
}

整个监控就是围绕这些状态标志和行为展开的。A任务定期修改自己的sign标志,sign是一个布尔变量,理论上只要A没有死,那么sign肯定是周期变化的(和心跳概念差不多),Stakeout需要做的就是监控sign的变化。为了避免一些偶然因素导致的误判,我们在Stakeout中设置了一些计数器和边界值(maydeadtimes和maydeadtimeout),当Stakeout发现A的的sign没有变化那就认为A是出现假死状态,如果连续3次出现假死状态那么判断A挂了,尝试重启A线程,如果总计出现了3次A挂了的情况,那么不在重启了,可以基本肯定不是由于偶然因素引起的,需要对A的代码或系统的环境进行检查。Stakeout会发出告警,通知必须要对A进行审查了,然后清空A,自己自动安全退出。如果在核心调度程序中设置一个标志接受Stakeout们的告警,就可以有足够理由终止其他任务的执行。 可以看见,在A任务发生异常期间,Stakeout承担了核心调度程序的维护功能。特别是当任务数量比较多的情况,核心调度程序只能采用排队方式处理任务异常,而且由于处理异常的复杂程度不同,无法保证对多任务异常的实时处理。
还要考虑正常情况下A和Stakeout的关系。核心调度程序通过Stakeout启动A任务后,Stakeout处于持续监控状态,当A正常结束任务后,A需要通知Stakeout结束监控,这样,当A进入休眠状态后,Stakeout也不会占用内存空间,提高了系统资源的利用率。