【问题标题】:Java - Thread and Static variableJava - 线程和静态变量
【发布时间】:2020-08-06 14:11:34
【问题描述】:

刚开始使用 java 中的线程,我无法对我的程序的输出进行推理

public class ThreadExample extends Thread{
    private int info;
    static int x = 0;

    public ThreadExample (int info) {
        this.info = info;
    }

    public void run () {
        if ( info == 1 )    {
            x = 3;
            System.out.println(Thread.currentThread().getName() + " " + x);
        } else{
            x = 1;
            System.out.println(Thread.currentThread().getName() + " " + x);
        }
    }

    public static void main (String args []) {
        ThreadExample aT1  = new ThreadExample(1);
        ThreadExample aT2  = new ThreadExample(2);
        aT1.start();
        aT2.start();
        System.err.println(x);
    }
}

输出:

Thread-0 3
Thread-1 1
3

为什么即使第二个线程将静态变量的值更改为 1,它也会打印 3

会有3个线程同时运行吗?

【问题讨论】:

    标签: java multithreading java-threads thread-sleep static-variables


    【解决方案1】:

    如果您在一个线程中更改变量,则它不会立即(或必需永远)对第二个线程可见,除非您使用某种同步原语,例如 Mutex。您还可以使用 AtomicInteger 等原子类来确保在一个线程中所做的更改对另一个线程可见。

    in the documentation 提供更多信息。

    【讨论】:

    • 我认为volatile 可能是您正在寻找的。这不是原子更新的问题,而是线程缓存的问题。
    • volatile 实际上还不够。像 Java 中的 x = 2 这样的代码行不能保证在所有平台上都是一条指令。因此,一个线程的 1/2 写入的 volatile 可能变得可见,然后另一个线程的写入的 1/2 可能会变得可见,最终结果可能是乱码。您需要确保原子性或互斥性。
    • @OliverDain This 不这么说
    • 谢谢@user7。不知道 Java 内存模型保证了 volatiles 的原子性。
    【解决方案2】:

    两种可能的情况

    1. 线程 2 会在线程 1 之前更新 x。您无法根据您看到的打印语句的顺序确定两个线程之间的执行交错​​方式。

    2. 线程确实按照您期望的顺序执行。但由于x 不是volatile,您可能看不到更新后的值。

    见 - What is the volatile keyword useful for

    【讨论】:

      【解决方案3】:

      你无法预测线程的结果。

      如果您在另一台设备上运行代码或多次运行代码,情况可能会有所不同。

      您不能(或不应该)依赖时间或调度程序。


      我认为并发/非易失性本身可能不是唯一的问题,但刷新也是您可能需要考虑的问题:

      x=3 (ThreadExample(1))
      sysout 3 (ThreadExample(1))
      syserr x (main thread)
      x=1 (ThreadExample(2))
      sysout 3 (ThreadExample (2))
      flush stdout (caused by jvm exit)
      flush stderr (caused by jvm exit)
      

      注意最后的冲洗。 stdout 和 stderr 可能不同步。

      这些流被缓冲并随时写入控制台。

      虽然可以保证写入 stdout 或 stderr 的两件事以正确的顺序写入,但如果您将一件事写入 stdout 并将另一件事写入 stderr,则不能保证这一点。

      还保证打印到stdoutstderr 的所有内容都在jvm 正常终止时写入(没有kill -9 或类似的)。

      如果 jvm 在stderr 之前写入stdout,则可以得到结果。


      如果您希望正确打印输出,您可能需要做两件事:

      • 打印后手动调用flush

      • 围绕操作、printlnflush 创建一个 synchronized 块(或类似块)。 (请注意,您可能会失去一些性能/并行性)

      如果您想测试刷新是否对您的情况有影响,请在您的 profram 末尾添加System.err.flush();(以便在stdout 之前刷新stderr),看看是否有区别。


      另外,我在其他答案中没有找到更多的东西,明确地:JIT 优化。

      JIT 编译器可能会对您的程序进行优化。例如,它可以优化:

      x=3;
      System.out.println(x);
      

      到:

      x=3;
      System.out.println(3);
      

      以便它打印3,即使在调用println 时它不是3

      【讨论】:

        【解决方案4】:

        不推荐使用变量在线程之间交换信息。对消息使用 BlockingQueues,对信号使用 Semaphores 和 CountDownLatches。简而言之,一个值的传递不仅要进行静默赋值,还要创建某种事件,以通知其他线程。我喜欢此类对象的“令牌”一词。

        【讨论】:

          【解决方案5】:

          会有3个线程同时运行吗?

          是的。第一个线程是主线程,它启动了这一切,它调用了您的public static void main (String args []) 方法。所有代码都在一个线程上运行。然后您的 main 方法启动 2 个线程。既然你从 1 开始,你现在有 3。

          至于为什么主线程的最终输出是3 很难回答,因为你有竞争条件。您有 3 个线程读取一个变量,而其中 2 个线程更新,这些都是同时发生的。

          x = 3;
          System.out.println(Thread.currentThread().getName() + " " + x);
          

          在运行 3 个线程的情况下,很容易假设上面 System.out.println 的输出将是 3,但实际情况是,在将其设置为 3 之后,另一个线程可能已经更新了它,然后当你打印它时,不再是 3。

          还要考虑volatile 关键字。没有它,JVM 可能会在线程中缓存共享值的副本,这可能导致跨线程读写时过时。 What is the volatile keyword useful for

          【讨论】:

            【解决方案6】:

            线程的结果是不可预测的。

            为了确保一致/可预测的行为,使用 volatile/Atomic 值使更改对其他线程可见

            【讨论】:

              猜你喜欢
              • 2012-01-15
              • 1970-01-01
              • 1970-01-01
              • 2013-03-29
              • 2013-08-31
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-07-17
              相关资源
              最近更新 更多