为什么 java 中的 while(true) 在线程中没有像我预期的那样工作

why while(true) in java not working as i expected in thread

我正在使用其他 jframe 执行任务。现在,我有 2 个 jframes,当我单击添加新按钮时位于 jframe1。接下来,jframe2 将显示,我想在单击时从这个 jframe 捕获事件,所以我使用线程和 while(true) 但它没有按我预期的那样工作,在我单击 jframe2 上的添加之后,jframe1 上的线程没有捕获并且它仍然循环无穷。我调试了,它工作正常,但是当我 运行 它不像我调试时那样工作。有什么问题吗?
这是我在 jframe1 中的代码:

JButton btnNewButton = new JButton("add new");
    btnNewButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            Thread t = new Thread() {
                public void run() {
                    System.out.println("start");
                    Frame2 f2 = new Frame2();
                    do {
                        if (f2.getCheck() == 0) {
                            System.out.println("catched");
                            break;
                        }
                    } while (true);
                    System.out.println("end");
                };
            };
            t.start();
            
        }
    });

这里是我在 jframe2 中的代码:

JButton btnNewButton = new JButton("add");
    btnNewButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            check = 0;
            frame.dispose();
        }
    });

Java 内存模型是罪魁祸首。

JVM 有一个邪恶的硬币。

它有时会掷硬币。具体来说,无论何时您访问任何字段,JVM 都会抛出邪恶的硬币。

Heads,JVM 将为您提供它先前缓存的此字段中包含的值。它在该线程中的值,与其他线程写入它的任何更新无关。

尾巴,它会为您提供任何线程最后写入的值。

它是邪恶的,因为它不是一枚公平的硬币:今天,在你的单元测试期间,作为调试器 运行s,在这个特殊的月相下,它每时每刻都有效时间。明天和下周也一样。但是 2 周后,就在您向重要客户提供该演示时?可靠地失败。

关键点是:如果你让 JVM 永远抛硬币,你就输了

不要让 JVM 翻转它。

要禁用 JVM 的邪恶硬币,您必须建立 so-called happens-before/happens-after 关系。

HB/HA 关系的工作方式如下:

  • 对于整个代码库中任意位置的任意 2 条语句,这 2 条语句要么有 HB/HA,要么没有。
  • 如果它们确实有 HB/HA,那么 JVM gua运行 告诉你 happens-after 行无法观察到任何状态,因为它是 before[=76] =] happens-before 行, 除了计时 。换句话说,代码的执行就像与 'happens after' 行具有 'happens before' 关系的语句实际发生过一样。它实际上并不需要,但你无法观察到它没有,所以这无关紧要。
  • 然而,如果他们没有 HB/HA,任何事情都可能发生。你可以观察它,也可以不观察它,即使你有绝对的证据表明 B 行 运行 在 A 行之后,那么 B 行可能会或可能不会观察到 A 的任何变化,JVM 的判断:邪恶的硬币翻转发生。

你可能会觉得这一切都非常愚蠢,而且显然很奇怪;为什么 JVM 会有一个邪恶的硬币,它为什么要玩这些游戏?

答案是效率。 Inter-core 通信非常昂贵。 JVM 经常但不总是尝试 运行 高效编码,这通常涉及本地缓存副本。 JVM 不会每时每刻都检查它是否无事可做,并以某种合理的方式积极地将所有这些缓存同步在一起,这 far 效率太低了。 CPUs 还有其他事情要做,即使他们不这样做,嘿,笔记本电脑是一回事,电费也是一回事,数据中心配置的 CPU 周期计数也是如此,所以花费 CPU 不必要的循环是不应该发生的浪费。

所以 JVM 没有。如果需要,它会很高兴地自行决定不同步或不同步几天。

就是你观察到的。随着调试器的启动,各种效率都不是 运行 因为它们会混淆调试器,因此您从线程 A 观察到 check = 0 在线程 B 将其设置为该值后许多秒。但是,如果没有打开调试器,您可能会打印一些信息,告诉您 B 已将 check 设置为 0,但随后 A 发现检查连续几天都不是 0。

换句话说,在线程 B 中,check 似乎为 0。您可以打印时间并确认这发生在时间点 X。然而,即使它是 很明显很久以后,A 仍然 观察到 check 仍然是 3 或 A 将其设置为 0 之前的任何状态。解释是B和A各有一份check。 JVM 不会因为你想要它而同步它们。而且没有简单的syncItUp()方法。而且,是的,也没有 gua运行tee 这些本地副本在那里。 JVM 可以自由同步或不同步它们。打开调试器后,它会同步 em。没有它,它不会。很奇怪,但是 JVM 的规范赋予了 JVM 执行此操作的能力。邪恶的硬币。保证 运行 的唯一方法是建立 HB/HA.

要建立HB/HA,嗯,你可以上网搜索一下。但是,简单的方法是:

  • synchronized关键字:如果线程A退出一个在objectref上同步到objectX的块,即gua运行teed到'happen before' 稍后进入此类同步块的任何其他线程。
  • 线程开始。如果线程 A 启动线程 B,则 b.start(); 之前的行是相对于线程 B 中第一行的 HB。
  • volatile。写入 volatile 字段建立 HB/HA 与读取,尽管如果它实际上早于 运行s(这些不是锁),你不会得到 gua运行tees。不过,在您的情况下,它会 'work'。
  • 内部使用这些东西的任何 java 库工具。例如,java.util.concurrent 包中的几乎所有内容。

所以,简而言之,volatile。但更一般地说,尝试从不同的线程读取和写入相同的字段是 Here Be Dragons,它们会咬掉你的头!领地.很难正确地编写代码,如果你搞砸了,单元测试将无法捕捉到它。您将拥有一个非常适合您的古怪应用程序,但在其他机器上或在奇怪的情况下会失败。

如果可以,请使用适当的工具(j.u.c 类 是一个好的开始),并尽量不要从多个线程写入字段。例如,通过具有适当 t运行saction 管理的数据库进行所有 inter-thread 通信。