假设我有一个全局对象:
class Global {
public static int remoteNumber = 0;
}
有一个线程定期运行以从远程获取新号码并更新它(仅写入):
new Thread {
@override
public void run() {
while(true) {
int newNumber = getFromRemote();
Global.remoteNumber = newNumber;
Thread.sleep(1000);
}
}
}
并且有一个或多个线程随机使用此全局remoteNumber
(仅读取):
int n = Global.remoteNumber;
doSomethingWith(n);
你可以看到我没有使用任何锁或synchronize
来保护它,这是正确的吗?是否存在可能导致问题的潜在问题?
更新
就我而言,读取线程必须实时获取最新的新值并不重要。我的意思是,如果有任何问题(由于缺少锁定/同步)导致一个阅读线程错过了该值,那么这并不重要,因为它很快就会有机会运行相同的代码(可能在循环中)< / p>
但是不允许读取未确定的值(我的意思是,如果旧值为20,则新的更新值为30,但读取线程会读取不存在的值,如33,我不确定是否它是可能的)
答案 0 :(得分:4)
你需要在这里进行同步(有一点需要注意,我稍后会讨论)。
主要问题是读者线程可能永远不会看到编写器线程所做的任何更新。通常最终会看到任何给定的写入。但是在这里你的更新循环是如此简单,以至于写入可以很容易地保存在缓存中,而不会将其发送到主内存。所以你真的必须在这里同步。
编辑11/2017 我要对此进行更新,并说可能在缓存中长时间保存一个值可能并不现实。我认为这是一个问题,尽管像这样的变量访问可以由编译器优化并保存在寄存器中。因此仍然需要同步(或volatile
)来告诉优化器确保为每个循环实际获取新值。
因此,您需要使用volatile
,或者需要使用(静态)getter和setter方法,并且需要在两种方法上使用synchronized
关键字。对于偶尔这样的写作,volatile
关键字 重量更轻。
需要注意的是,如果你真的不需要从写线程中看到及时更新,那么你就不必同步了。如果无限期延迟不会影响您的程序功能,则可以跳过同步。但是在计时器上这样的事情看起来不像是省略同步的好用例。
编辑:在 Java Concurrency in Practice 中,根据Brian Goetz,Java / JVM不允许向您显示&#34;不确定&#34;值 - 从未写过的值。这些在技术上被称为&#34;凭空而来#34; Java规范不允许使用这些值。您可以保证看到先前对您的全局变量进行的一些写操作,无论是初始化的零还是后续写入,都不允许其他值。
答案 1 :(得分:2)
读取线程可以读取未确定时间的旧值,但实际上没有问题。因为每个线程都有自己的变量副本。有时他们同步。您可以使用volatile关键字删除此优化:
public static volatile int remoteNumber = 0;