解决“变量可能尚未初始化”错误

时间:2012-03-06 07:20:13

标签: java multithreading timer threadpool

public final void sendAdvertisement(final Advertisement advertisment, int delay, final int repetitions){
    final ScheduledFuture exec = executor.scheduleAtFixedRate( //<< initialized on this line
        new Runnable(){
            int totalSends = 0;
            public void run(){
                //do stuff here

                if(++totalSends >= repetitions) exec.cancel(true); //<< here is says exec might not be initialized
            }
        },
    0, delay, TimeUnit.MILLISECONDS);
}

如果无法做到这一点,您能建议更好的方法吗?我在ScheduledThreadPoolExecutor中找不到它的方法。基本上我要做的是让这段代码运行3次然后取消“计时器”。我可以使用Swing Timer,但我不想因为它用于其他东西。

它在评论中说,但这是错误:

%PATH%Discovery.java:148: variable exec might not have been initialized
                if(++totalSends >= repetitions) exec.cancel(true);

3 个答案:

答案 0 :(得分:2)

您的代码可能会从删除编译器警告的角度起作用,但警告的重点是指出您可能正在访问尚未分配的变量。即使execexec[0]为非null,也无法保证ScheduledFuture对象甚至已正确初始化 - 即使内部线程可能正在运行,也是如此。这是非常危险的,可能会工作一段时间,但是当你转移到具有更多内核或不同负载环境的架构时,它会在生产中大幅失败。它也可以工作,但是从现在开始一个月后你就会更改do stuff here代码并开始失败。

我看到了几种可以更好地完成此任务的方法。它们更复杂但也更安全并且与Java一致。首先想到的是使用AtomicReference

final AtomicReference<ScheduledFuture> futureReference =
    new AtomicReference<ScheduledFuture>();
ScheduledFuture exec = executor.scheduleAtFixedRate(
    new Runnable() {
        int totalSends = 0;
        public void run() {
            //do stuff here
            if (++totalSends >= repetitions) {
                // we need to wait for the future to be initialized
                while (futureReference.get() == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        Thread.currentThread.().interrupt();
                    }
                }
                futureReference.get().cancel(true);
            }
        }
    },
    0, delay, TimeUnit.MILLISECONDS);
futureReference.set(exec);

答案 1 :(得分:1)

我基本上有两种方法来解决这个问题。不幸的是,对于最终的代码质量,它们都不是特别好,所以我不确定我是否可以推荐它们。

第一个解决方案是使exec成为最终的单元素数组。然后你可以在声明后指定exec [0] =某事,即使数组本身是最终的。这种变化是使用/创建一些引用类(因为您可以更改最终引用的属性,但不能更改引用本身)。以下是一个简单的示例,但请记住,它不会考虑任何并发问题(请参阅下文):

    final ScheduledFuture[] exec = new ScheduledFixture[1];
    exec[0] = executor.scheduleAtFixedRate( //<< initialized on this line
            new Runnable(){
                int totalSends = 0;
                public void run(){
                    //do stuff here

                    if(++totalSends >= repetitions) exec[0].cancel(true); //<< here is says exec might not be initialized
                }
            },
        0, delay, TimeUnit.MILLISECONDS);

或者,您可以将exec移出方法的本地范围,并使其成为类属性。

但是,我必须警告你,特别是在初始延迟为零的情况下,在scheduleAtFixedRate方法返回之前,runnable中的代码有可能被执行,在这种情况下,exec [0]仍然为null。此外,您应该使用同步来确保主线程设置的exec [0]的值可用于负责执行runnable的线程。

上述两种解决方案都应该有效,但我认为其中任何一种都不是特别好。

答案 2 :(得分:1)

为什么在知道执行次数时使用固定费率调度程序, 我认为简单的循环将完成这项工作

for (int i = 0; i < iterations; i++) {
    executor.schedule(new Runnable() {
        public void run() {
            // do stuff here
        }
    }, delay * i, TimeUnit.MILLISECONDS);
}

正如WalterM所说:这不是创建许多新实例的好方法,请在循环中使用引用。