【问题标题】:Working around the "variable might not have been initialized" error解决“变量可能尚未初始化”错误
【发布时间】:2012-03-06 07:20:13
【问题描述】:
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,但我不想因为它用于其他东西。

它在 cmets 中说,但这是错误:

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

【问题讨论】:

  • 最终 ScheduledFeature exec = null; exec = executor.sche... 等等。然后在你的 if(++totalSends) 语句中,添加一个辅助语句 if(exec != null){exec.cancel(true);} ???
  • 这不可能,你不能在初始化后改变一个最终变量,即使是空值对吗?
  • 哎呀,你是对的。让它不是最终的。
  • 没关系,我删除了我的答案,我错了。如果将其设为非最终版本,则无法在可运行文件中访问它。我的错。
  • 它必须是有趣的,否则我将无法在匿名内部类中使用它

标签: java multithreading timer threadpool


【解决方案1】:

从消除编译器警告的角度来看,您的代码可能会起作用,但警告的重点是指出您可能正在访问尚未分配的变量。即使execexec[0] 不为空,也不能保证ScheduledFuture 对象已被正确初始化——是的,即使内部线程可能正在运行。这是非常危险的,可能会工作一段时间,但当您迁移到具有更多内核的架构或在不同的负载情况下时,会在生产中严重失败。它也可能有效,但是从现在开始一个月后您更改 do stuff here 代码并开始失败。

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

// this class handles atomic updates and memory synchronization
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 (true) {
                    ScheduledFuture future = futureReference.get();
                    if (future != null) {
                        future.cancel(true);
                        break;
                    }
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        Thread.currentThread.().interrupt();
                    }
                }
            }
        }
    },
    0, delay, TimeUnit.MILLISECONDS);
// this sets the future reference so the thread can use it
futureReference.set(exec);

【讨论】:

  • 是的,但它几乎是一样的,我做了检查 if(exec[0]==null);就个人而言,我认为它们是一样的。我永远不会使用 Thread.sleep();虽然在线程池中。我认为这是个坏主意。
  • == null 不是,伙计。优化器可以从初始化器返回一个值,但选择稍后进行实际的代码初始化。这与双重检查锁定错误相同:javaworld.com/javaworld/jw-02-2001/jw-0209-double.html
  • 在睡眠方面,我个人认为旋转比睡眠要危险。如果你真的需要,你可以做一个sleep(1)
  • @WalterM 请记住,sleep(1) 仅用于解决竞争条件问题,因此应该很少被点击。
  • 不,在使用之前检查它是否为空,我没有这样做 if(exec[0]==null)exec[0]=new Blah( );我在做 if(exec[0]!=null)//使用它。所以应该没有任何问题
【解决方案2】:

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

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

    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 移出方法的本地范围并使其成为类属性。

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

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

【讨论】:

  • 谢谢,这是有道理的。我使用了大小为 1 的数组,因为我希望它只是该方法的本地。它可能不会为空,因为线程需要一些时间才能开始,但我理解它为什么会发生。所以我会考虑到这一点。
  • 对不起,我真的不同意这个答案。它可以解决编译问题,但没有保证 exec[0] 不为空,即使它不为空,对象也可能尚未完全初始化——即使 Runnable 正在运行。这种解决方案可能运行良好,然后在生产中、在不同负载下或在更改 do stuff here 中完成的工作量之后会严重失败。
  • 这就是我在倒数第二段中特别警告此类问题的原因。当然,按照您的建议使用 AtomicReference 在各个方面都肯定要好得多。我不知道我是怎么设法忽略了这门课这么久的。
【解决方案3】:

既然知道执行次数,为什么还要使用固定速率调度程序, 我认为简单的 for 循环就可以完成这项工作

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

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

【讨论】:

  • 这是个好主意,但我不想为每次迭代都创建一个新的 Runnable。特别是如果它可能超过 10 或什么的。实在是太多了。
  • 现在我想起来了,其实你说对了一半。如果您在循环之前创建 Runnable,然后在每次迭代中传递相同的 Runnable 对象。你实际上不需要一个新的。
  • 是的,当然——我的目标是执行问题,而不是创建许多实例的问题。你最终是如何解决这个问题的?
猜你喜欢
  • 2016-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多