示例代码:
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”易变或AtomicReference?
如果答案是否定的,那么我应该将其改为:
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;
}
}
答案 0 :(得分:7)
所有Lock实现必须强制执行内置监视器锁提供的相同内存同步语义:
- 成功的锁定操作就像成功的monitorEnter操作
- 成功的解锁操作就像一个成功的monitorExit操作
如果在两个线程中使用Lock
(即读取和写入),则读取线程将看到新值,因为monitorEnter
会刷新缓存。否则,您需要声明变量volatile
以强制从读取线程中的内存中读取。
答案 1 :(得分:1)
根据布莱恩定律...
如果您正在编写一个可能接下来被另一个读取的变量 线程,或读取可能最后写入的变量 另一个线程,你必须使用同步,进一步,两者 读者和作者必须使用相同的监视器锁同步。
所以同步setter和getter是合适的......
或
如果您想避免 AtomicInteger.incrementAndGet()
块(即同步块),请使用lock-unlock
代替
答案 2 :(得分:1)
如果我有一个线程不断调用getV,我只需要进行setV 另一个线程,是读取线程保证看到新的值 写完之后呢?
不,读取线程可能只读取自己的副本(由运行读取线程的CPU核心自动缓存)V
的值,因此不会获得最新值。
或者我是否需要将“V”设为volatile或AtomicReference?
是的,它们都有效。
使V
volatile简单地停止CPU Core缓存V
的值,即对变量V
的每次读/写操作都必须访问主内存,这比较慢(约100x)比从L1缓存读取的速度慢一点,详见interaction_latency)
使用V = new AtomicInteger()
有效,因为AtomicInteger
在内部使用private volatile int value;
来提供可见性。
而且,如果你在读取和编写线程时使用lock(Lock
对象,synchronized
块或方法;它们都可以工作),它也有效(因为你的第二个代码段有),因为(根据到Second Edition of The Java ® Virtual Machine Specification部分8.9)
...从概念上锁定任何锁都会刷新线程中的所有变量 工作记忆,并解锁任何锁定强制写入主要 对线程分配的所有变量的记忆......
...如果线程仅在锁定a后才使用特定的共享变量 特别锁定,并在相应的解锁之前 锁定,然后线程将从中读取该变量的共享值 锁定操作后的主存储器,如有必要,将复制回来 到主存储器最近分配给该变量的值 在解锁操作之前。这与共同相结合 锁的排除规则,足以保证值 通过共享正确地从一个线程传输到另一个线程 变量...
P.S。 AtomicXXX
类还提供CAS
(比较和交换)操作,这对mutlthread访问很有用。
P.P.S。自Java 6以来,关于此主题的jvm规范没有更改,因此它们不包含在jvm specification for java 7, 8, and 9中。
P.P.P.S。根据{{3}},无论每个核心的视图如何,CPU缓存始终是连贯的。您的问题中的情况是由“内存订购缓冲区”引起的,其中store
&可以重新排序load
指令(用于从内存中写入和读取数据)以获得性能。详细地说,缓冲区允许load
指令超越较早的store
指令,这正是导致问题的原因(getV()
被提前,因此它在您更改之前读取该值另一个线程)。但是,在我看来,这更难以理解,因此“不同核心的缓存”(如JVM规范所做的那样)可能是一个更好的概念模型。
答案 3 :(得分:0)
你应该使用volatile或AtomicInteger。这将确保阅读线程最终会看到变化,并且在大多数情况下足够接近“紧随其后”。从技术上讲,你不需要像这样的简单原子更新。仔细看看AtomicInteger的API。 set(),compareAndSet()等等......都会通过原子方式读取线程来设置值。
答案 4 :(得分:0)
显式锁,synchronized
,原子引用和volatile
都提供内存可见性。锁定synchronized
对它们所包围的代码块和原子引用以及volatile
这样做的特定变量执行此操作。但是,为了使可见性正常工作,读取和写入方法都应该受到相同锁定对象的保护。
它不适用于你的情况,因为你的getter方法不是由保护setter方法的锁投射的。如果您进行更改,它将按要求工作。此外,只需将变量声明为volatile
或AtomicInteger
或AtomicReference<Integer>
也可以。