来自Java语言规范的易失​​性示例返回令人惊讶的行为

时间:2013-11-14 12:18:36

标签: java volatile jls

我想亲自尝试Java lang spec example,但显然我有些不明白。我的理解是,易失性计数器的递增顺序应该与代码中出现的顺序相同。 “令人惊讶的是”我得到一个随机计数器值,因为一个计数器有时更少,相等和更大。 有没有人可以解释我错过的东西?

下面的代码和输出:

public class C {

private static volatile int i = 0;
private static volatile int j = 0;

static void one() {
    i++;
    j++;
}

static void two() {
    int a = i;
    int b = j;
    if(a < b)
        System.out.println(a + " < " + b);
    if(a > b)
        System.out.println(a + " > " + b);
    if(a == b)
        System.out.println(a + " = " + b);
}

public static void main(String[] args) throws Exception {
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            while(true)
                one();
        }
    });
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            while(true)
                two();
        }
    });

    t1.start();
    t2.start();

    Thread.sleep(5000);

    System.exit(0);
}
}

输出

214559700 > 214559699
214559807 > 214559806
214559917 > 214559916
214560019 = 214560019
214560137 > 214560136
214560247 = 214560247
214560349 > 214560348
214560455 = 214560455
214560561 > 214560560
214560670 = 214560670
214560776 = 214560776
214560886 > 214560885
214560995 = 214560995
214561097 < 214561098

5 个答案:

答案 0 :(得分:1)

对于所有三种情况,假设我们从i = 0j = 0开始......一个()和两个()都可以执行两个操作,但是一个和两个之间的顺序是未定义:

a == b

two() loads i (a = 0)
two() loads j (b = 0)
one() increments i (i = 1)
one() increments j (j = 1)

a&gt; B'/强>

one() increments i (i = 1)
two() loads i (a = 1)
two() loads j (b = 0)
one() increments j (j = 1)

a&lt; b (稀有)

two() loads i (a = 0)
one() increments i (i = 1)
one() increments j (j = 1)
two() loads j (b = 1)

答案 1 :(得分:0)

重要的是要注意线程可以通过多种方式进行交错。由于您要将ij复制到ab,因此您可以修改<{1}}和i 之间的<{1}} / em>将j复制到i,将a复制到j。例如,这种痕迹可以解释你的输出:

某些初始状态,然后复制

  • 背景:i == 214559700,j == 214559699
  • T2:a = i,b = j

    b

增加7次,然后递增! -ment

  • T1:(i ++ j ++)(x7)
  • T1:i ++
  • T2:a = i,b = j

    214559700 > 214559699
    
  • T1:j ++

增加9次,然后递增! -ment

  • T1:(i ++ j ++)(x9)
  • T1:i ++
  • T2:a = i,b = j

      214559807 > 214559806
    
  • T1:j ++

增加2倍,然后复制

  • T1:(i ++ j ++)(x2)
  • T2:a = i,b = j

    214559917 > 214559916
    

答案 2 :(得分:0)

对i和j的访问不同步,因此,以下所有情况都可能发生:

  • t2读取i,t2读取j,t1写入i,t1写入j
  • t2读取i,t1写入i,t2读取j,t1写入j
  • t1写入i,t2读取i,t2读取j,t1写入j
  • t1写i,t2读i,t2读j,t2读i,t2读j,t1写j
  • 等。等

答案 3 :(得分:0)

当其中一个线程刚刚完成递增i并希望开始递增j时,另一个线程可能已经设置了int ab,因此导致不同的价值观。

volatile具有不同的用途,因为我认为你认为它有用。实质上,volatile用于表示变量的值将被不同的线程修改。 volatile修饰符保证读取字段的任何线程都将看到最近写入的值

声明一个易变的Java变量意味着:

  • 此变量的值永远不会被线程本地缓存:所有读取和写入将直接进入“主存储器”;
  • 对变量的访问就好像它被包含在synchronized块中一样,自身同步。

Source and more explanation.

当您想要从不同的线程而不是不同的成员访问相同的volatile成员时,volatile的使用会变得更加清晰。

答案 4 :(得分:0)

如果我们先阅读j

static void two() {
    int b = j;
    int a = i;

然后保证a> = b。