15

示例代码:

class Sample{
    private int v;
    public void setV(){
        Lock a=new Lock();
        a.lock();
        try{
            v=1;
        }finally{
            a.unlock();
        }
    }
    public int getV(){
        return v;
    }
}

如果我有一个线程不断调用 getV 并且我只在另一个线程中执行一次 setV ,那么该读取线程是否保证在写入后立即看到新值?还是我需要使“V”易失或原子参考?

如果答案是否定的,那么我应该将其更改为:

class Sample{
    private int v;
    private Lock a=new Lock();
    public void setV(){
        a.lock();
        try{
            v=1;
        }finally{
            a.unlock();
        }
    }
    public int getV(){
        a.lock();
        try{
            int r=v;
        }finally{
            a.unlock();
        }
        return r;
    }
}
4

5 回答 5

10

文档中:

所有 Lock 实现必须强制执行与内置监视器锁提供的相同的内存同步语义:

  • 成功的锁定操作就像成功的 monitorEnter 操作
  • 成功的解锁操作就像成功的 monitorExit 操作

如果您Lock 在两个线程中使用(即读取和写入线程),读取线程将看到新值,因为monitorEnter刷新缓存。否则,您需要声明该变量volatile以在读取线程中强制从内存中读取。

于 2012-09-14T18:23:52.050 回答
1

根据布赖恩定律...

如果您正在写入一个可能接下来被另一个线程读取的变量,或者读取一个可能最后被另一个线程写入的变量,则必须使用同步,此外,读取器和写入器都必须使用相同的监视器锁进行同步.

所以同步setter和getter是合适的......

或者

如果您想避免AtomicInteger.incrementAndGet()该块(即同步块) ,请改用lock-unlock

于 2012-09-14T18:20:10.950 回答
1

如果我有一个线程不断调用 getV 并且我只在另一个线程中执行一次 setV ,那么该读取线程是否保证在写入后立即看到新值?

不,读取线程可能只是读取了自己的值的副本(由正在运行读取线程的 CPU 内核自动缓存)V的值,因此无法获得最新的值。

还是我需要使“V”易失或原子参考?

是的,它们都有效。

使Vvolatile 简单地阻止 CPU Core 缓存V的值,即对变量的每次读/写操作都V必须访问主内存,这比较慢(比从 L1 Cache 读取慢大约 100 倍,详情请参阅interaction_latency

使用V = new AtomicInteger()有效,因为在内部AtomicInteger使用 aprivate volatile int value;来提供可见性。

而且,如果您在读写线程(就像您的第二个代码段那样)上使用锁(Lock对象、块或方法;它们都有效),它也可以工作,因为(根据Java ® 虚拟机规范的第二版第 8.9 节)synchronized

...锁定任何锁在概念上会刷新线程工作内存中的所有变量,解锁任何锁会强制将线程分配的所有变量写入主内存...

...如果线程仅在锁定特定锁之后和相应解锁该相同锁之前使用特定共享变量,则线程将在锁定操作之后从主内存中读取该变量的共享值,如果需要,并且将在解锁操作之前将最近分配给该变量的值复制回主存储器。这与锁的互斥规则相结合,足以保证值通过共享变量从一个线程正确传输到另一个线程......

PS这些AtomicXXX类还提供CAS(比较和交换)操作,这对多线程访问很有用。

PPS 关于这个主题的 jvm 规范自 Java 6 以来没有改变,因此它们不包含在java 7、8 和 9 的 jvm 规范中

PPPS 根据这篇文章,无论从每个内核的角度来看,CPU 缓存始终是一致的。您问题中的情况是由“内存排序缓冲区”引起的,其中store&load指令(相应地用于从内存中写入和读取数据)可以重新排序以提高性能。详细地说,缓冲区允许一条load指令领先于一条较旧的store指令,这正是导致问题的原因(getV()放在前面,因此它在您在另一个线程中更改它之前读取该值)。但是,在我看来,这更难理解,因此“不同内核的缓存”(正如 JVM 规范所做的那样)可能是一个更好的概念模型。

于 2017-12-21T14:29:46.630 回答
0

您应该制作 volatile 或 AtomicInteger。这将确保阅读线程最终会看到更改,并且对于大多数目的来说足够接近“马上”。从技术上讲,您不需要 Lock 来进行像这样的简单原子更新。仔细看看 AtomicInteger 的 API。set()、compareAndSet() 等...都将通过原子读取线程将值设置为可见。

于 2012-09-14T18:19:50.133 回答
0

显式锁、synchronized原子引用和volatile都提供内存可见性。锁定并synchronized为它们包围的代码块和原子引用以及volatile声明的特定变量执行此操作。但是,为了使可见性正常工作,读取和写入方法都应该受到同一个锁定对象的保护。

它不适用于您的情况,因为您的 getter 方法不是由保护 setter 方法的锁投射的。如果您进行更改,它将按要求工作。也只是将变量声明为volatileor AtomicIntegerorAtomicReference<Integer>也将起作用。

于 2012-09-14T18:21:59.790 回答