说这static意味着所有对象的值的volatile一个副本并且意味着所有线程的值的一个副本是否正确?
无论如何,static变量值也将成为所有线程的一个值,那么我们为什么要这样做volatile呢?
我读过" 什么时候在Java中使用'volatile'? "但我仍然感到困惑.我怎么知道何时应该标记变量volatile?如果我弄错了,要么在需要它的东西上省略volatile,要么在不需要的东西上放置volatile呢?在确定多线程代码中哪些变量应该是易变的时,有哪些经验法则?
我正在阅读Java中的volatile关键字并完全理解它的理论部分.
但是,我正在寻找的是一个很好的案例,它展示了如果变量不是易变的话会发生什么.
下面的代码片段无法正常工作(来自aioobe)
class Test extends Thread {
boolean keepRunning = true;
public void run() {
while (keepRunning) {
}
System.out.println("Thread terminated.");
}
public static void main(String[] args) throws InterruptedException {
Test t = new Test();
t.start();
Thread.sleep(1000);
t.keepRunning = false;
System.out.println("keepRunning set to false.");
}
}
Run Code Online (Sandbox Code Playgroud)
理想情况下,如果keepRunning不是volatile,则线程应该继续无限运行.但是,它会在几秒钟后停止.
我有两个基本问题: -
Joe Albahari有一个关于多线程的伟大系列,这是一本必须阅读的内容,对于任何进行C#多线程处理的人来说都应该被人们所熟知.
然而,在第4部分中,他提到了易变的问题:
请注意,应用volatile不会阻止写入后读取交换,这可以创建脑筋急转弯.Joe Duffy通过以下示例很好地说明了这个问题:如果Test1和Test2在不同的线程上同时运行,则a和b最终都可能为0(尽管在x和y上都使用了volatile)
接下来是MSDN文档不正确的说明:
MSDN文档指出使用volatile关键字可确保始终在字段中显示最新值.这是不正确的,因为正如我们所见,可以重新排序读取后跟读取.
我查看了MSDN文档,该文档最后一次在2015年更改但仍列出:
volatile关键字表示某个字段可能被同时执行的多个线程修改.声明为volatile的字段不受编译器优化的约束,这些优化假定由单个线程进行访问.这可确保始终在字段中显示最新值.
现在我仍然避免使用volatile来支持使用陈旧数据的更冗长的线程:
private int foo;
private object fooLock = new object();
public int Foo {
get { lock(fooLock) return foo; }
set { lock(fooLock) foo = value; }
}
Run Code Online (Sandbox Code Playgroud)
关于多线程的部分是在2011年写的,这个论点今天仍然有效吗?是否应该不惜一切代价避免使用volatile或完全内存防护,以防止引入非常难以产生的错误,如上所述甚至依赖于它运行的CPU供应商?
假设我有一个静态复杂对象,它由一个线程池定期更新,并在一个长时间运行的线程中或多或少地连续读取.对象本身总是不可变的,反映了最近的某种状态.
class Foo() { int a, b; }
static Foo theFoo;
void updateFoo(int newA, int newB) {
f = new Foo();
f.a = newA;
f.b = newB;
// HERE
theFoo = f;
}
void readFoo() {
Foo f = theFoo;
// use f...
}
Run Code Online (Sandbox Code Playgroud)
我至少不在乎我的读者是看到旧的还是新的Foo,但是我需要看到一个完全初始化的对象.IIUC,Java规范说没有HERE中的内存屏障,我可能会看到一个fb初始化但尚未提交到内存的对象.我的程序是一个真实世界的程序,它迟早会将内容提交到内存中,所以我不需要立即将新的值ofFoo提交到内存中(虽然它不会受到伤害).
您认为实现内存屏障最可读的方式是什么?如果需要,我愿意为了可读性而付出一点性能价格.我想我可以将赋值同步到Foo,这样可行,但是我不确定读取代码的人为什么这么做是非常明显的.我还可以同步新Foo的整个初始化,但这会引入更多实际需要的锁定.
你会如何编写它以使其尽可能可读?
对Scala版本的奖励荣誉:)
我知道在java中,如果你有多个线程访问未标记为volatile的变量,你可能会遇到一些意想不到的行为.
例:
private boolean bExit;
while(!bExit) {
checkUserPosition();
updateUserPosition();
}
Run Code Online (Sandbox Code Playgroud)
如果将bExit变量标记为voilatile,那么可以保证其他线程将看到最新的值.
c#的行为方式是否相同?
更新
例如,在C#中执行此操作:
int counter = ...;
for(...)
{
new Thread(delegate()
{
Interlocked.Decrement(ref counter);
}
}
if(counter == 0)
{
// halt program
}
Run Code Online (Sandbox Code Playgroud)
在上面,在c#中,你是否必须将计数器变量标记为volatile,否则它将按预期工作?