繁体   English   中英

变量'runner'在循环内不更新

[英]Variable 'runner' is not updated inside loop

像这样,我有两个线程。 SleepRunner 线程将一些随机数添加到列表中,然后将标志更改为 true 并进入睡眠状态。 主线程等待 SleepRunner 线程,直到 SleepRunner object 中的标志由 false 变为 true,然后主线程将中断 SleepRunner 线程,程序结束。

但问题是,当 while 循环在主线程中没有主体代码时,变量 'runner' 不会在循环内更新,换句话说 SleepRunner 线程将标志从 false 更改为 true 后程序没有结束。 于是尝试在idea中使用debug工具,程序顺利结束。 如果我在主线程的 while 循环体中编写一些代码,如 System.out.println() 或 Thread.sleep(1) ,程序也成功结束。 太不可思议了? 有谁知道为什么会这样。 谢谢。

public class Test1 {
        public static void main(String[] args) {
            SleepRunner runner = new SleepRunner();
            Thread thread = new Thread(runner);
            thread.start();
            while(!(runner.isFlag())){
                /*try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
            }
            System.out.println("END");
            thread.interrupt();
        }
    }

public class SleepRunner implements Runnable {
        private boolean flag = false;
    
        public boolean isFlag() {
            return flag;
        }
    
        @Override
        public void run() {
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep((long) (Math.random() * 200));
                }
                catch (InterruptedException e) {
                    System.out.println("Interrupted");
                }
                int num = (int) (Math.random() * 100);
                System.out.println(Thread.currentThread().getName() + " " + num);
                list.add(num);
            }
    
            flag = true;
    
            System.out.println("30 Seconds");
            try {
                Thread.sleep(30000);
            }
            catch (InterruptedException e) {
                System.out.println("Interrupted in 30 seconds");
            }
            System.out.println("sleep runner thread end");
        }
    }

你违反了 java memory model。

以下是 JMM 的工作原理*:

每当读取更新任何字段(来自任何对象)时,每个线程都会抛硬币。 在头上,它将制作一个副本并从中更新/读取。 在尾巴上,它不会。 您的工作是确保您的代码能够正常运行,而不管硬币是如何落地的,并且您不能在单元测试中强制抛硬币。 硬币不必是“公平的”。 硬币的行为取决于音乐播放器中播放的音乐、幼儿的奇思妙想以及月相。 (换句话说,任何更新/读取都可以对本地缓存副本进行,也可以不进行,直到 java 实现)。

您可以安全地得出结论,正确执行此操作的唯一方法是确保线程永远不会抛硬币。

实现这一目标的方法是建立所谓的“先于”关系。 建立它们主要是通过使用同步原语,或通过调用使用同步原语的方法来完成的。 例如,如果我这样做:

线程 X:

synchronized(x) {
    x.foo();
    System.out.println(shared.y);
    shared.y = 10;
}

线程 Y:

synchronized(x) {
    x.foo();
    System.out.println(shared.y);
    shared.y = 20;
}

那么您已经建立了一种关系:代码块 A 在代码块 B 之前出现,反之亦然,但您至少已经确定它们必须按顺序运行。

因此,这将保证打印0 100 20 没有同步块,它也可以合法地打印0 0 所有 3 个结果都是可接受的结果(java 语言规范说没问题,并且您认为这没有意义的任何错误都将被视为“按预期工作”)。

也可以使用volatile ,但 volatile 的作用相当有限。

通常,由于无法对其进行充分测试,因此在 java 中只有 3 种方法可以正确执行线程:

  1. 'in the large':使用网络服务器或其他负责多线程的应用程序框架。 您不编写psv main()方法,该框架编写,您编写的只是“处理程序”。 您的处理程序根本没有接触任何共享数据。 处理程序要么不共享数据,要么通过旨在正确执行数据的总线共享数据,例如可序列化事务隔离模式下的数据库,或 rabbitmq 或其他消息总线。
  2. 'in the small':使用 fork/join 并行化一个巨大的任务。 当然,任务的处理程序不能使用任何共享数据。
  3. 阅读 Concurrency in Practice(书籍),更喜欢使用 java.util.concurrent package 中的类,并且通常成为了解这些东西如何工作的大师,因为以任何其他方式进行线程处理可能会导致您编程错误,而您测试可能不会捕获,但要么会在生产时崩溃,要么会导致没有实际的多线程(例如,如果你过度同步所有东西,你最终会得到除了一个核心之外的所有核心都在等待,你的代码实际上会运行比单线程慢得多)。

*) 完整的解释是关于一本书的价值。 我只是给你过度简化的亮点,因为这只是一个 SO 答案。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM