2

我想自己尝试Java lang 规范示例,但显然有些东西我不明白。我的理解是 volatile 计数器的递增顺序应该与代码中显示的顺序相同。“令人惊讶的是”我得到一个随机计数器值,因为一个计数器有时小于、等于和大于另一个。有没有人可以解释我错过了什么?

下面的代码和输出:

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
4

5 回答 5

1

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

a == b

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

a > b

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

a < b(较少见)

two() loads i (a = 0)
one() increments i (i = 1)
one() increments j (j = 1)
two() loads j (b = 1)
于 2013-11-14T12:40:27.947 回答
0

重要的是要注意线程可以以多种方式交错。由于您正在复制iandjaand b,因此您可以在复制到i和intoj 之间进行修改。例如,这种跟踪将解释您的输出:iajb

一些初始状态,然后复制

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

    214559700 > 214559699
    

递增 7 次,然后递增——复制!——ment

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

      214559807 > 214559806
    
  • T1:j++

递增 9 次,然后递增——复制!——ment

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

    214559917 > 214559916
    
  • T1:j++

增加2倍,然后复制

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

    214560019 = 214560019
    
于 2013-11-14T12:27:40.443 回答
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
  • 等等等等
于 2013-11-14T12:28:29.617 回答
0

当一个线程刚刚完成递增i并想要开始递增j时,另一个线程可能已经设置了 int 的ab,从而导致不同的值。

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

声明一个 volatile Java 变量意味着:

  • 这个变量的值永远不会被缓存到线程本地:所有的读写都将直接进入“主内存”;
  • 对变量的访问就像它被封闭在一个synchronized块中一样,在其自身上同步。

来源和更多解释。

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

于 2013-11-14T12:28:57.207 回答
0

如果我们j先阅读,

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

那么保证a>=b。

于 2013-11-14T16:49:50.100 回答