如果 getter 被标记为同步,为什么这段代码会完成?
Why this code finishes if getter is marked as synchronized?
尽管字段 value 不是可变的,但方法 get()
被标记为同步时为什么这段代码成功完成?如果没有同步,它会在我的机器上无限期地运行(如预期的那样)。
public class MtApp {
private int value;
/*synchronized*/ int get() {
return value;
}
void set(int value) {
this.value = value;
}
public static void main(String[] args) throws Exception {
new MtApp().run();
}
private void run() throws Exception {
Runnable r = () -> {
while (get() == 0) ;
};
Thread thread = new Thread(r);
thread.start();
Thread.sleep(10);
set(5);
thread.join();
}
}
同步强制 this.value = value
到 发生在 get()
.
之前
它确保了更新值的可见性。
没有同步,就没有这样的保证。 可能有效,也可能无效。
对于初学者来说,value
需要 volatile
或者 get
和 set
都需要 synchronized
才能正确。
JLS 17.4.5:
An unlock on a monitor happens-before every subsequent lock on that monitor.
有可能在释放锁之前将 value
设置为 5
,这将其置于 happens-before 边缘之前并且使其在下次获取锁时可用。
应该注意的是,这种保证是脆弱的,并且取决于线程调度程序,可能根本不存在。在同步模型较弱的平台上,您可能看不到与此处相同的效果。
另请参阅:
Loop doesn't see changed value without a print statement
@Andy Turner 部分正确。
在 get()
方法上添加 synchronized
会影响内存可见性要求,并导致 (JIT) 编译器生成不同的代码。
但是,严格来说,happens before 关系连接 set(...)
调用和 get()
调用。这意味着 set
方法应该是 synchronized
以及 get
(如果你打算这样做!)。
简而言之,您观察到的代码版本不能保证在所有平台和所有情况下都能正常工作。事实上,你很幸运!
从字里行间看出,您似乎正试图通过实验弄清楚 Java 的内存模型是如何工作的。 这不是一个好主意。问题是您正试图对一个极其复杂的黑匣子进行逆向工程,但没有足够的"input parameters"1 供您改变以涵盖黑盒行为的所有潜在方面。
因此,"learning by experiment" 方法可能会使您产生不完整或错误的理解。
如果你想要一个完整和准确的理解,你应该开始阅读一本好的教科书中的Java内存模型......或者JLS本身.无论如何,使用实验来尝试确认您的理解,但您确实需要知道 JMM 指定(保证)仅如果您做正确的事情会发生什么。如果您做错了事,您的代码 可能 仍然有效……取决于各种因素。因此,通常很难通过实验确认某种特定的做事方式是正确的还是不正确的2.
1 - 您需要的一些参数实际上并不存在。例如,允许您 运行 Java N for N > 12 的那个,或者允许您在您无权访问的硬件上 运行 的那个......或者那还不存在。
2 - 如您的示例所示。您得到的是 "right" 答案,即使代码是错误的。
尽管字段 value 不是可变的,但方法 get()
被标记为同步时为什么这段代码成功完成?如果没有同步,它会在我的机器上无限期地运行(如预期的那样)。
public class MtApp {
private int value;
/*synchronized*/ int get() {
return value;
}
void set(int value) {
this.value = value;
}
public static void main(String[] args) throws Exception {
new MtApp().run();
}
private void run() throws Exception {
Runnable r = () -> {
while (get() == 0) ;
};
Thread thread = new Thread(r);
thread.start();
Thread.sleep(10);
set(5);
thread.join();
}
}
同步强制 this.value = value
到 发生在 get()
.
它确保了更新值的可见性。
没有同步,就没有这样的保证。 可能有效,也可能无效。
对于初学者来说,value
需要 volatile
或者 get
和 set
都需要 synchronized
才能正确。
JLS 17.4.5:
An unlock on a monitor happens-before every subsequent lock on that monitor.
有可能在释放锁之前将 value
设置为 5
,这将其置于 happens-before 边缘之前并且使其在下次获取锁时可用。
应该注意的是,这种保证是脆弱的,并且取决于线程调度程序,可能根本不存在。在同步模型较弱的平台上,您可能看不到与此处相同的效果。
另请参阅: Loop doesn't see changed value without a print statement
@Andy Turner 部分正确。
在 get()
方法上添加 synchronized
会影响内存可见性要求,并导致 (JIT) 编译器生成不同的代码。
但是,严格来说,happens before 关系连接 set(...)
调用和 get()
调用。这意味着 set
方法应该是 synchronized
以及 get
(如果你打算这样做!)。
简而言之,您观察到的代码版本不能保证在所有平台和所有情况下都能正常工作。事实上,你很幸运!
从字里行间看出,您似乎正试图通过实验弄清楚 Java 的内存模型是如何工作的。 这不是一个好主意。问题是您正试图对一个极其复杂的黑匣子进行逆向工程,但没有足够的"input parameters"1 供您改变以涵盖黑盒行为的所有潜在方面。
因此,"learning by experiment" 方法可能会使您产生不完整或错误的理解。
如果你想要一个完整和准确的理解,你应该开始阅读一本好的教科书中的Java内存模型......或者JLS本身.无论如何,使用实验来尝试确认您的理解,但您确实需要知道 JMM 指定(保证)仅如果您做正确的事情会发生什么。如果您做错了事,您的代码 可能 仍然有效……取决于各种因素。因此,通常很难通过实验确认某种特定的做事方式是正确的还是不正确的2.
1 - 您需要的一些参数实际上并不存在。例如,允许您 运行 Java N for N > 12 的那个,或者允许您在您无权访问的硬件上 运行 的那个......或者那还不存在。
2 - 如您的示例所示。您得到的是 "right" 答案,即使代码是错误的。