为什么子线程看不到更改后的静态变量? JMM 或 volatile
why the subthread can not see the changed static variable ? JMM or volatile
这里是 volatile
关键字的用法示例。
public class Test3{
public static volatile boolean stop = false;// if volatile is not set, the loop will not stop
public static void main(String[] args) throws InterruptedException{
Thread thread = new Thread(){
public void run() {
int i=0;
while(!stop){
i++;
// add this line
// System.out.println(i);
// or this block
// try {
// Thread.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
}
};
thread.start();
Thread.sleep(2000);
stop = true;
}
}
很容易理解,如果设置了volatile
,JVM应该在检查它的值的同时从内存中加载更新的值,然后while循环可以按预期停止。但问题是,静态变量不应该同时改变吗?可能会有一些延迟,但最终应该检测到这种变化。不?我测试过,如果我们添加一些打印代码或睡眠代码,是否可以检测到这种变化?有人可以教我为什么喜欢吗?也许是关于 JMM。
It's easy to understand that if volatile is set, JVM should load the
updated value from memory while checking its value, then the while
loop can stop as expected.
volatile
is some kind of rule
or mechanism
, not about concrete implemention above. It is used to build happends-before
线程关系:
Using volatile variables reduces the risk of memory consistency
errors, because any write to a volatile variable establishes a
happens-before relationship with subsequent reads of that same
variable. This means that changes to a volatile variable are always
visible to other threads. What's more, it also means that when a
thread reads a volatile variable, it sees not just the latest change
to the volatile, but also the side effects of the code that led up the
change.
如果没有 volatile
或其他同步,静态变量 的更新可以 被其他线程看到,但有一些延迟,而 不能 永远被看到,因为 memory barriar。 不确定。即使您添加了一些打印代码或睡眠代码,并且发现它可以工作,但这并不意味着它在其他环境或其他时刻仍然可以工作。
但是,如果您在 while loop
和 main
线程中都添加打印代码:
while(!stop){
i++;
System.out.println(i);
}
和
stop = true;
System.out.println("some");
JMM 可以保证stop = true
会在循环中检测到(至少在oracle jdk 8u161 上),这是因为System.out.println
是synchronized
,这也可以在涉及的线程之间建立happens-before
关系,查看源码:
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
时间,在 wall-clock 时间的意义上,对内存可见性没有意义。重要的是同步操作之间的同步顺序。读取和写入非volatile
字段不是同步操作,因此在没有任何其他同步操作的情况下,它们之间没有顺序。
所以即使主线程在一年前完成,所以从主线程的角度来看肯定是写的,子线程可能会一直运行,运行ning;从它的角度来看,写入并没有发生。它也不知道主线程已经终止。请注意,执行能够检测到另一个线程已终止的操作是可以建立顺序的同步操作。
但由于实际的程序行为还取决于 JIT 编译器和优化器,因此某些代码更改可能会产生影响,即使不能保证。
例如插入 sleep
并不意味着任何内存可见性:
JLS §17.3. Sleep and Yield:
It is important to note that neither Thread.sleep
nor Thread.yield
have any synchronization semantics. In particular, the compiler does not have to flush writes cached in registers out to shared memory before a call to Thread.sleep
or Thread.yield
, nor does the compiler have to reload values cached in registers after a call to Thread.sleep
or Thread.yield
.
但这可能会阻止优化器将循环视为需要优化的热点。
当您插入 System.out.println
语句时,PrintStream
的内部同步可能会对整体内存可见性产生影响,但也不能保证这种影响,因为主线程会在 PrintStream
.
上不同步
顺便说一下,甚至不能保证相同优先级的线程之间会发生抢占式线程切换。因此,如果 JVM 在调用 start()
之后尝试完成您的子线程,然后再将 CPU 返回主线程,这将是一个有效的执行。
在那个执行场景中,循环中没有sleep
,子线程永远不会放弃CPU,所以stop
永远不会被设置为true
,即使声明为 volatile
。这将是避免轮询循环的另一个原因,尽管现实生活中可能没有没有抢占式线程切换的执行环境。今天的大多数执行环境甚至有多个 CPU,因此不放弃 CPU 不会阻止其他线程执行。
不过,为了形式上正确,您应该在写入 stop
变量和读取变量之间强制执行排序,例如声明变量 volatile
并插入一个可能当 quit
仍然是 false
.
时,导致线程最终释放 CPU
这里是 volatile
关键字的用法示例。
public class Test3{
public static volatile boolean stop = false;// if volatile is not set, the loop will not stop
public static void main(String[] args) throws InterruptedException{
Thread thread = new Thread(){
public void run() {
int i=0;
while(!stop){
i++;
// add this line
// System.out.println(i);
// or this block
// try {
// Thread.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
}
};
thread.start();
Thread.sleep(2000);
stop = true;
}
}
很容易理解,如果设置了volatile
,JVM应该在检查它的值的同时从内存中加载更新的值,然后while循环可以按预期停止。但问题是,静态变量不应该同时改变吗?可能会有一些延迟,但最终应该检测到这种变化。不?我测试过,如果我们添加一些打印代码或睡眠代码,是否可以检测到这种变化?有人可以教我为什么喜欢吗?也许是关于 JMM。
It's easy to understand that if volatile is set, JVM should load the updated value from memory while checking its value, then the while loop can stop as expected.
volatile
is some kind of rule
or mechanism
, not about concrete implemention above. It is used to build happends-before
线程关系:
Using volatile variables reduces the risk of memory consistency errors, because any write to a volatile variable establishes a happens-before relationship with subsequent reads of that same variable. This means that changes to a volatile variable are always visible to other threads. What's more, it also means that when a thread reads a volatile variable, it sees not just the latest change to the volatile, but also the side effects of the code that led up the change.
如果没有 volatile
或其他同步,静态变量 的更新可以 被其他线程看到,但有一些延迟,而 不能 永远被看到,因为 memory barriar。 不确定。即使您添加了一些打印代码或睡眠代码,并且发现它可以工作,但这并不意味着它在其他环境或其他时刻仍然可以工作。
但是,如果您在 while loop
和 main
线程中都添加打印代码:
while(!stop){
i++;
System.out.println(i);
}
和
stop = true;
System.out.println("some");
JMM 可以保证stop = true
会在循环中检测到(至少在oracle jdk 8u161 上),这是因为System.out.println
是synchronized
,这也可以在涉及的线程之间建立happens-before
关系,查看源码:
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
时间,在 wall-clock 时间的意义上,对内存可见性没有意义。重要的是同步操作之间的同步顺序。读取和写入非volatile
字段不是同步操作,因此在没有任何其他同步操作的情况下,它们之间没有顺序。
所以即使主线程在一年前完成,所以从主线程的角度来看肯定是写的,子线程可能会一直运行,运行ning;从它的角度来看,写入并没有发生。它也不知道主线程已经终止。请注意,执行能够检测到另一个线程已终止的操作是可以建立顺序的同步操作。
但由于实际的程序行为还取决于 JIT 编译器和优化器,因此某些代码更改可能会产生影响,即使不能保证。
例如插入 sleep
并不意味着任何内存可见性:
It is important to note that neither
Thread.sleep
norThread.yield
have any synchronization semantics. In particular, the compiler does not have to flush writes cached in registers out to shared memory before a call toThread.sleep
orThread.yield
, nor does the compiler have to reload values cached in registers after a call toThread.sleep
orThread.yield
.
但这可能会阻止优化器将循环视为需要优化的热点。
当您插入 System.out.println
语句时,PrintStream
的内部同步可能会对整体内存可见性产生影响,但也不能保证这种影响,因为主线程会在 PrintStream
.
顺便说一下,甚至不能保证相同优先级的线程之间会发生抢占式线程切换。因此,如果 JVM 在调用 start()
之后尝试完成您的子线程,然后再将 CPU 返回主线程,这将是一个有效的执行。
在那个执行场景中,循环中没有sleep
,子线程永远不会放弃CPU,所以stop
永远不会被设置为true
,即使声明为 volatile
。这将是避免轮询循环的另一个原因,尽管现实生活中可能没有没有抢占式线程切换的执行环境。今天的大多数执行环境甚至有多个 CPU,因此不放弃 CPU 不会阻止其他线程执行。
不过,为了形式上正确,您应该在写入 stop
变量和读取变量之间强制执行排序,例如声明变量 volatile
并插入一个可能当 quit
仍然是 false
.