您的位置:首页 > 编程语言 > Java开发

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

2017-01-17 14:42 447 查看
   

Bug来源:

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

// class MailCache
private 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 init
A init
b bug=0
A other
B 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;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: