请参见下面的代码:
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
}
无论何时,当一个线程进入同步块时,是否会从主存储器中加载变量标志的值?
谢谢您的详细解释,因为我不确定该标志是否具有事前关系。从字面上看,该标志不在同步块中。
更新1:
我知道使用volatile可以,而且我也知道如何编写正确的代码,但是我现在想知道是否没有volatile关键字。是否同步可以保证可见性。注意:flag变量不在同步块中。
更新2:
我再次更新了代码,我的win10 + JDK8系统上的代码可以正常停止,您认为它是正确的还是偶然的,因为尚未在所有硬件系统上进行测试,因此我需要理论指导。
关注问题:
循环条件(标志变量)是否与循环内的同步块具有先发生关系,如果存在先发生关系,jvm是否确保已从主存储器加载标志变量,即使标志变量不在同步块中。
如果每个人都认为没有事前发生的关系,那么您如何解释当我删除同步块时,代码将无限期地循环。当我添加它时,它将正常停止。这只是意外吗?
答案 0 :(得分:3)
再仔细一点看代码,您所拥有的还不够。对共享字段的访问位于您的synchronized
块之外,因此不行。
此外,Java要求共享内存的读取和写入都必须以某种方式“同步”。使用synchronized
键域,通常意味着您需要在读取和写入时都使用它,而没有显示写入。
此外,用于给定的一组字段或共享内存的“锁”对于读取和写入必须是相同的锁。严重的是,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
是的。不能保证内存可见性,但不是禁止的。可以出于任何原因使内存可见,即使只是“偶然”。在Intel平台上,Intel具有QPI总线,可绕过内存总线高速交换内存更新信息。但是,即使可以通过软件解决,所以最好只是将同步放在需要的位置(提示:查看AtomicBoolean
。)
答案 1 :(得分:1)
由于@xTrollxDudex和@markspace提供的信息,循环部分中的代码是从jvm级别观察的,如果没有 事前发生关系,并且代码可以从:
进行优化 while (flag){
synchronized (lock){
// some work
}
}
至:
if(flag){
while (true){
synchronized (lock){
//some work
}
}
}
为确保线程可见性,我们需要避免这种优化,例如通过volatile关键字或其他同步策略。 循环中同步块的外观类似于增强型volatile关键字的功能,该功能保证了变量前面的可见性,因此当我们第二次循环进入同步块时,可以看到最新的。更改,这就是循环可以正常停止的原因。看起来不错,但这不是正确的同步方法,所以不要这样做。
有关详细说明,请参见here
中的类似问题