Java中static变量相互引用导致的Bug

来源:互联网 发布:王师就剩一个连 知乎 编辑:程序博客网 时间:2024/04/28 16:25

   

Bug来源:

    这个问题是在实现storm慢请求报警功能时,MailCache类引用了Environments的静态方法。

// class MailCacheprivate static final UrlMap DEFAULT_URLMAP = Environments.getDefaultUrlMap();

在Environments类中,我想在类被初始化时就开一个定时更新cache的定时器,就把它放在了static初始化块中,而updateCache引用了MailCache,因此,MailCache构造函数执行时,它的静态初始化过程还未完成。

public static Map<String, Integer> mRegex2Threshold;public static ScheduledExecutorService mScheduExec;  static {    updateCache();//updateCache会执行MailCache的静态方法    mScheduExec.scheduleAtFixedRate(new Runnable() {        @Override        public void run() {            updateCache();        }    }, 15, 15, TimeUnit.SECONDS);}

​   Bug简化描述:

    在多个类的加载过程中,类加载的顺序是不确定的。当类A引用B时,如果B类还未被加载,则会在本线程中暂停A的执行,去加载B,然后继续执行A,称这个过程是Load_B

    类被加载后,一个类或实例的初始化过程是:

    step 1、静态变量(初始化块) 

    step 2、非静态变量(初始化块) 

    step 3、构造函数

    step 2和 step 3的发生必须有new关键字的触发。

    如果Load_B发生在A类的step1时,则会造成A的初始化过程被打断,会一起一些问题,比如,A的构造函数会在A静态变量未初始化完成之前被执行!

    如下例:

    

public class B {    static {        System.out.println("B init");    }    static int hook = A.hook;    static int bug = 3;     static {        System.out.println("B other");    }     public B() {        System.out.println("b bug=" + bug);    }     public static void main(String... args) {     }}   public class A {    static {        System.out.println("A init");    }     static int hook;    static B b = new B();     static {        System.out.println("A other");    }}


    当初始化到class A的b变量时,执行class B的构造函数,而此时class B的静态变量还未初始化完毕,也就是构造函数被提前执行了。    由于main方法的存在,class B先被加载,然后执行step 1。当初始化到 hook变量时,发现class A未加载,所以转而加载class A,然后执行A的step 1。

    以上程序运行输出:

B initA initb bug=0A otherB other

    可以看出,class B构造函数执行时,bug变量的值并不是3,而是int类型的默认值0。


    当A和B的逻辑更复杂时,这样的Bug难以定位。造成该Bug的本质原因是A和B存在相互引用的static变量,有以下解决方法:

    1、把引用代码,如hook=A.hook放在最下面,即让它成为静态初始化的最后一步。但这样做,代码难以维护,维护者可能会莫名其妙地调到坑里。

   2、把涉及相互引用的代码变成非static变量,然后用单例模式保证变量的唯一,改造后的代码:

public class A {    static {        System.out.println("A init");    }    static int hook;    B b = new B();    static {        System.out.println("A other");    }    static A _holder = null;    private A() {    }    public static A getInstance() {        if (_holder == null) {            synchronized (A.class) {                if (_holder == null) {                    _holder = new A();                }            }        }        return _holder;    }}


0 0