如果在围绕同步锁的循环中使用变量,是否会读取 "fresh from main memory"?
Will a variable be read "fresh from main memory" if used in a loop AROUND a synchronized lock?
请看下面的代码:
private static boolean flag=true; // main thread will call flag=false
private final static Object lock=new Object(); // lock condition
public static void thread1(){
while (flag){
synchronized (lock){
// some work
}
}
}
public static void main(String[] args) throws Exception {
Thread t1=new Thread(()->{
thread1();
});
t1.start();
Thread.sleep(1000);
flag=false;
// The program can stop normally
}
无论任何时候,当一个线程进入synchronized块时,是否会从主存中加载变量flag的值?
感谢您的详细解释,因为我不确定flag是否有happend-befores关系。从字面上看,标志不在同步块中。
更新1:
我知道使用 volatile 可以,我也知道如何编写正确的代码,但我现在想知道是否没有 volatile 关键字。是否synchronized可以保证可见性。注意:标志变量不在同步块中。
更新2:
我又更新了代码,我win10+JDK8系统上的代码可以正常停止,你觉得是正确还是偶然,因为没有在所有硬件系统上测试,所以需要理论指导。
关注问题:
循环条件(flag变量)是否和循环内部的同步块有happen-before关系,如果有happen-before关系,jvm是否保证flag变量从主存加载即使标志变量不在同步块中。
如果大家都认为没有happen-before关系,那你怎么解释当我去掉synchronized块后,代码会无限循环。当我添加它时,它会正常停止。这只是意外吗?
好的,仔细看一下您的代码,您拥有的还不够。对共享字段的访问在您的 synchronized
块之外,所以不,它不起作用。
此外,Java要求共享内存的读写都"synchronized"不知何故。使用synchronized
keyworld,这通常意味着您需要在读取和写入时都使用它,并且您没有显示写入。
除此之外,用于给定字段集或共享内存的 "lock" 必须是用于读取和写入的相同锁。说真的,这里 volatile
更容易,java.util.concurrent
中的 API 更容易,推荐。不要试图重新发明轮子。
private static boolean flag = true; // must use 'resetFlag'
public void resetFlag() { synchronized( "lock" ) {flag = false;} }
public boolean getFlag() { synchronized( "lock" ) {return flag;} }
public void thread1() {
while ( getFlag() ){
synchronized ("lock"){
// other work
}
}
}
public static void main(String[] args) throws Exception {
Thread t1=new Thread(()->{
thread1();
});
t1.start();
Thread.sleep(1000);
resetFlag();
// The program can stop normally
}
我认为上面有必要的修改。
关于您的第二次更新:the code on my win10+JDK8 system can stop normally
是的,可以。不保证内存可见性,但不禁止。内存可以出于任何原因可见,甚至只是 "accidentally." 在 Intel 平台上,Intel 有一个 QPI 总线,可以绕过内存总线高速交换内存更新信息。然而,即使这也可以通过软件解决,所以最好只在需要的地方进行同步(提示:查看 AtomicBoolean
。)
感谢@xTrollxDudex和@markspace提供的资料,loop部分的代码是从jvm层面观察的,如果没有
happens-before 关系和代码可以从 :
优化
while (flag){
synchronized (lock){
// some work
}
}
至:
if(flag){
while (true){
synchronized (lock){
//some work
}
}
}
为了保证线程可见性,我们需要避免这种优化,比如通过volatile关键字或者其他同步策略。
sync block在循环中的出现类似于增强型volatile关键字的作用,保证了前面变量的可见性,所以当我们第二次循环进入sync block时,可以看到latest .的变化,这就是循环可以正常停止的原因。看起来不错,但不是正确的同步方法,所以不要这样做。
详细解释见here
类似问题
请看下面的代码:
private static boolean flag=true; // main thread will call flag=false
private final static Object lock=new Object(); // lock condition
public static void thread1(){
while (flag){
synchronized (lock){
// some work
}
}
}
public static void main(String[] args) throws Exception {
Thread t1=new Thread(()->{
thread1();
});
t1.start();
Thread.sleep(1000);
flag=false;
// The program can stop normally
}
无论任何时候,当一个线程进入synchronized块时,是否会从主存中加载变量flag的值?
感谢您的详细解释,因为我不确定flag是否有happend-befores关系。从字面上看,标志不在同步块中。
更新1:
我知道使用 volatile 可以,我也知道如何编写正确的代码,但我现在想知道是否没有 volatile 关键字。是否synchronized可以保证可见性。注意:标志变量不在同步块中。
更新2:
我又更新了代码,我win10+JDK8系统上的代码可以正常停止,你觉得是正确还是偶然,因为没有在所有硬件系统上测试,所以需要理论指导。
关注问题:
循环条件(flag变量)是否和循环内部的同步块有happen-before关系,如果有happen-before关系,jvm是否保证flag变量从主存加载即使标志变量不在同步块中。
如果大家都认为没有happen-before关系,那你怎么解释当我去掉synchronized块后,代码会无限循环。当我添加它时,它会正常停止。这只是意外吗?
好的,仔细看一下您的代码,您拥有的还不够。对共享字段的访问在您的 synchronized
块之外,所以不,它不起作用。
此外,Java要求共享内存的读写都"synchronized"不知何故。使用synchronized
keyworld,这通常意味着您需要在读取和写入时都使用它,并且您没有显示写入。
除此之外,用于给定字段集或共享内存的 "lock" 必须是用于读取和写入的相同锁。说真的,这里 volatile
更容易,java.util.concurrent
中的 API 更容易,推荐。不要试图重新发明轮子。
private static boolean flag = true; // must use 'resetFlag'
public void resetFlag() { synchronized( "lock" ) {flag = false;} }
public boolean getFlag() { synchronized( "lock" ) {return flag;} }
public void thread1() {
while ( getFlag() ){
synchronized ("lock"){
// other work
}
}
}
public static void main(String[] args) throws Exception {
Thread t1=new Thread(()->{
thread1();
});
t1.start();
Thread.sleep(1000);
resetFlag();
// The program can stop normally
}
我认为上面有必要的修改。
关于您的第二次更新:the code on my win10+JDK8 system can stop normally
是的,可以。不保证内存可见性,但不禁止。内存可以出于任何原因可见,甚至只是 "accidentally." 在 Intel 平台上,Intel 有一个 QPI 总线,可以绕过内存总线高速交换内存更新信息。然而,即使这也可以通过软件解决,所以最好只在需要的地方进行同步(提示:查看 AtomicBoolean
。)
感谢@xTrollxDudex和@markspace提供的资料,loop部分的代码是从jvm层面观察的,如果没有 happens-before 关系和代码可以从 :
优化 while (flag){
synchronized (lock){
// some work
}
}
至:
if(flag){
while (true){
synchronized (lock){
//some work
}
}
}
为了保证线程可见性,我们需要避免这种优化,比如通过volatile关键字或者其他同步策略。 sync block在循环中的出现类似于增强型volatile关键字的作用,保证了前面变量的可见性,所以当我们第二次循环进入sync block时,可以看到latest .的变化,这就是循环可以正常停止的原因。看起来不错,但不是正确的同步方法,所以不要这样做。
详细解释见here
类似问题