【问题标题】:Strange java behavior with while loop and queue带有while循环和队列的奇怪java行为
【发布时间】:2012-11-27 09:18:27
【问题描述】:

我有奇怪的 while(true) 循环行为。代码如下:

作为班上的一员,我有:

static Queue<Object> response = new LinkedList<Object>();

...和一个函数:

private void read() {

    while (true)
    {
        System.out.println("foo");
        if(!(response.isEmpty()))
        {

            if((Boolean)response.peek() == true)
            {
                view.dispose();
                LogInControler controler= new LogInControler();
                disableMasterLogin();
                response.poll();
                return;
            }
            else if((Boolean)response.poll() == false)
            {
                JOptionPane.showMessageDialog(view.getRootPane(), 
                        "Wrong username or password.");
                view.tfUsername.requestFocus();
                return;
            }
        }
    }
}

当从服务器(通过 Socket)接收到对象时,InputController 类将该对象传递给适当的控制器,在本例中为 MasterLogInController 并将其放入队列响应中。 我正在等待 while(true) 循环中的响应,但问题是如果我删除“System.out.printline("foo");"循环只会输入一次!?使用这条 syso 行,我“强制”while 循环执行循环,直到收到响应。这里有什么问题?

【问题讨论】:

  • 你只是在while循环中输入了true,你需要指定什么是true。
  • 对我来说,这听起来像是某种竞争条件。您在启动的线程中调用它,对吗?尝试在此方法中捕获所有异常并打印它们。

标签: java while-loop queue


【解决方案1】:

我怀疑正在发生的事情是 JIT 编译器正在优化您的循环而不存在。如果response.isEmpty() 在您的循环中第一次被调用时为真,并且注意到response 不在synchronized 块或方法内,或者标记为volatile,则JIT 编译器可能会认为它不是将进行更改并从正在运行的代码中删除看似空的繁忙循环。

在 JIT 编译器看来,在 println() 语句中添加至少给循环一个目的,所以它会在这种情况下保持运行。

要解决此问题,除了 assylias 给出的重要建议外,您可以将所有对 response 的引用放在 synchronized 块中,如下所示:

public void read() {
    Boolean result = null;
    synchronized (response) {
        while (true) {
            result = (Boolean) response.poll();
            if (result != null) break;
            try {
                response.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
                // You could put return; here
            }
        }
    }
    // result should always be non null here
    if (result) {
         view.dispose();
         LogInControler controler = new LogInControler();
         disableMasterLogin();
    } else {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JOptionPane.showMessageDialog(view.getRootPane(), "Wrong username or password");
                view.tfUsername.requestFocus();
            }
        });
    }
}

如果您的其他线程将响应添加到队列中,请确保它也在同步块中并调用notifyAll()

public void addResult(Object result) {
    synchronized (response) {
        response.add(result);
        response.notifyAll();
    }       
}

【讨论】:

  • 我还把JOptionPane.showMessageDialog()requestFocus() 放在了invokeLater 的调用中,因为它们只能在EDT 上调用。
【解决方案2】:

我假设你有几个线程在运行。

System.out.println 创建一个内存屏障,这可能有助于您的代码看到一些不可见的变量(因为缺乏同步)。

特别是,您的队列不是线程安全的,并且似乎是安全发布的。所以很有可能:

  • 您的 while 循环可能会将 response 视为 null ==> NullPointerException
  • reponse.isEmpty() 可能会返回 false,但 response.peek() 可能会返回 null,然后您将其转换为 Boolean 并在您的条件下取消装箱 if((Boolean)xxx == true) ==> NullPointerException

除了 cmets 中给出的有助于理解原因的合理建议外,您还应该使代码线程安全。例如,您可以使用thread safe BlockingQueue。但这可能还不够(因为您的各种 if / if / else if 语句的布局方式以及队列可能在每个语句之间被另一个线程更改的事实)。

【讨论】:

  • 谢谢!这就解释了一切。
  • 我必须做的唯一改变是代替 Queue 我使用 BlockingQueue response = new LinkedBlockingQueue();但是仍然很奇怪为什么我有这个问题,而我的朋友在他的计算机上没有(我们有相同版本的 Java - 7u9)。
  • 它可能取决于许多因素,包括 JVM 参数(-client 或 -server)、处理器架构、内核数量、操作系统(例如 Windows 与 Linux)、其他程序使用的 CPU 负载等. 它在一台计算机上工作的事实只是巧合,不能依赖。如果您在朋友的计算机上多次运行该程序,它可能会在某个阶段中断。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多