【问题标题】:Volatile key was not working as expected易失性密钥未按预期工作
【发布时间】:2015-09-09 04:27:28
【问题描述】:

正在编写示例应用程序以了解易失行为。 根据我的说法,这个变量的值应该更改为50,但我得到的输出是10

主要方法:

public class Volatile {
    public static void main(String[] args) {
        ShareObj so = new ShareObj();
        Thread1 t1 =  new Thread1(so);
        Thread2 t2 =  new Thread2(so);
        t1.start();
        t2.start();
        System.out.println(so.a);
    }
}

类:

class ShareObj {
    public volatile int a =10; 
}

class Thread1 extends Thread {
    ShareObj so;
    Thread1(ShareObj so) {
        this.so  = so;
    }
    public void run() {
        so.a += 10;
        System.out.println(so.a);
    }
}

class Thread2 extends Thread {
    ShareObj so;
    Thread2(ShareObj so) {
        this.so=so;
    }
    public void run() {
        so.a+=10;
        System.out.println(so.a);
    }
}

我期望的输出是50,但它是10

有什么建议吗?

【问题讨论】:

  • 一方面,当main 运行System.out.println(so.a); 时,您的线程可能还没有运行一条语句(因为启动线程比打印内容要慢)
  • 为什么要 50 个?
  • 请注意,在此示例中,无论是否使用 volatile,在实践中,您都可能得到相同的结果。
  • 另外,实际上,如果您曾经认为您需要使用volatile,那么您有 99% 的机会在某处遇到竞争条件(与 volatile 无关) .

标签: java


【解决方案1】:

首先,您需要等待两个线程完成,然后再打印它们的结果。

...
t1.start();
t2.start();
t1.join(); // this will make main thread to wait untill thread is finished
t2.join();
.....

目前,您正在声明两个线程,但在它们中的任何一个可以更改 volatile 值之前,您的主线程是 pring 值并正在退出。

文档是here

【讨论】:

    【解决方案2】:

    你有两个问题:

    1. 正如@immibis 和@Beri 已经指出的那样,您的main 方法中的System.out.println(so.a); 可能会在您的线程完成之前运行。

    2. 您从10 开始a,并有两个线程,每个线程将其递增10,因此您应该期望30 而不是50

    main 方法更改为

    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        ShareObj so = new ShareObj();
        Thread1 t1 = new Thread1(so);
        Thread2 t2 = new Thread2(so);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(so.a);
    }
    

    代码按预期打印出30(如最后一行;请注意,两个线程都可能输出20 30,所以你不应该依赖前两个输出)。

    我还想推荐@user3707125's answer,指出volatile 可能仍然不会产生预期的结果(当您有更多线程和/或更多递增步骤时)-参见。还有this Stack Overflow question

    【讨论】:

      【解决方案3】:

      我正在考虑您提供的代码已被简化,并且您使用调试执行来确保在进行System.out.println 调用时两个线程都完成了它们的工作,否则您应该在打印结果之前使用Thread.join

      现在关于 volatile 关键字。 volatile 表示该变量没有线程缓存。但是,这并不意味着使用此变量的操作将以原子方式执行。

      代码so.a += 10如果简化的意思就是so.a = so.a + 10,这个操作由JVM分两步执行(为了简单起见,我们省略了字段访问):

      1. 计算so.a + 10
      2. so.a分配给计算结果

      现在让我们分析它如何影响执行(下一个案例发生的图像):

      1. thread_1:计算并入栈 (so.a + 10) => 0 + 10 => 10 (x)
      2. thread_2:计算并入栈 (so.a + 10) => 0 + 10 => 10 (y)
      3. thread_1:从堆栈分配so.a => so.a = x => so.a = 10
      4. thread_2:从堆栈分配so.a => so.a = y => so.a = 10
      5. main_thread: 打印so.a => print(10)

      因此,即使使用 volatile 关键字,使用您编写的代码也不安全。

      如果您想验证这种情况,请将线程代码更改为:

      for (int i = 0; i < 1000000000; i++) {
          so.a += 1;
      }
      

      你会看到结果总是不同的,几乎从来没有 2kkk。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-02-17
        • 1970-01-01
        • 1970-01-01
        • 2021-02-18
        • 2020-04-26
        • 2021-09-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多