id = 1 - id是原子吗?

Ada*_*zyk 74 java multithreading swap scjp atomic

从OCP Java SE 6程序员实践考试的第291页开始,问题25:

public class Stone implements Runnable {
    static int id = 1;

    public void run() {
        id = 1 - id;
        if (id == 0) 
            pick(); 
        else 
            release();
    }

    private static synchronized void pick() {
        System.out.print("P ");
        System.out.print("Q ");
    }

    private synchronized void release() {
        System.out.print("R ");
        System.out.print("S ");
    }

    public static void main(String[] args) {
        Stone st = new Stone();
        new Thread(st).start();
        new Thread(st).start();
    }
}
Run Code Online (Sandbox Code Playgroud)

其中一个答案是:

输出可能是 P Q P Q

我把这个答案标记为正确.我的推理:

  1. 我们开始两个线程.
  2. 第一个进入run().
  3. 根据JLS 15.26.1,它首先评估1 - id.结果是0.它存储在线程的堆栈中.我们正准备将其保存0为静态id,但......
  4. Boom,scheduler选择第二个线程来运行.
  5. 所以,第二个线程进入run().静态id仍然是1,所以他执行方法pick().P Q打印出来.
  6. 调度程序选择第一个线程运行.它0从堆栈中获取并保存为静态id.所以,第一个线程也执行pick()并打印P Q.

但是,在书中写道,这个答案是不正确的:

这不正确,因为线路id = 1 - id交换价值id之间01.没有机会同一方法执行两次.

我不同意.我认为上面提到的场景有一定几率.这种交换不是原子的.我错了吗?

Jon*_*eet 78

我错了吗?

不,你是绝对正确的 - 就像你的例子时间表一样.

除了它不是原子之外,不能保证写入id将被另一个线程拾取,因为没有同步且字段不是易失性的.

对于像这样的参考材料来说,这有点令人不安:(

  • 在CPU的引擎盖下面是龙.Jon谈论的特殊问题称为"缓存一致性".当你要求变量的值时,每次进入主存储器的速度太慢(比你想要的慢几百倍!).为了解决这个问题,现代处理器都具有CPU特定或特定于内核的内存缓存,它们首先要查看.这意味着一个线程可以在内存中更改`id`的"官方"值,而另一个线程永远不会看到它,因为它永远看不到它自己的缓存. (25认同)
  • @AdamStelmaszczyk:不,我的意思是线程1可以为`id`写一个新值,但是线程2可能不会立即看到它 - 它可能会看到*old*值. (9认同)
  • 值得注意的是,作者甚至[承认这个问题可能有问题](http://www.coderanch.com/t/531921/java-programmer-SCJP/certification/Threads-OCP-Java-Practice-Exams# post_text_2413290)(因为有),虽然我猜他们从不打扰提出勘误列表. (9认同)
  • 有一整套工具可以处理缓存一致性,从显式缓存刷新命令到原子到同步.幸运的是,对于那些从未想过要处理这些龙的人来说,与"synchronized"或互斥体的同步通常会"你认为应该做什么",只要你使用它们来保护你的数据使用标准模式(比如上例中的`synchronized`).他们弄错了`id`真是太遗憾了. (6认同)