java - 同步和可变变量

java - synchronization and volatile variable

我从 this article 那里读到:

...Synchronized blocks also guarantee that all variables accessed inside the synchronized block will be read in from main memory, and when the thread exits the synchronized block, all updated variables will be flushed back to main memory again, regardless of whether the variable is declared volatile or not.

Effective Java中还有一个示例:

public class StopThread {
    private static boolean stopRequested;
    
    private static synchronized void requestStop() {
        stopRequested = true;
    }
    
    private static synchronized boolean stopRequested() {
        return stopRequested;
    }
    
    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(() -> {
            int i = 0;
            while (!stopRequested()) i++
        });
        backgroundThread.start();
        
        TimeUnit.SECONDS.sleep(1);
        requestStop();
    }
}

变量 stopRequested 未声明为 volatile 并且作者声明“......换句话说,这些方法的同步仅用于其 通信effects,不是为了互斥...”。但我想知道,在访问数组元素或访问synchronized method/block中对象的字段的情况下,我们能否始终保证内存可见性,无需 必须手动强制对数组元素进行易失性访问(例如通过使用 Unsafe#getObjectVolatile)或在对象字段上声明 volatile 修饰符?谢谢!

// flags is an instance of a custom class. Should Flags.stopRequested be declared volatile?
public class StopThread {
    private static Flags flags = new Flags(); 
    
    private static synchronized void requestStop() {
        flags.stopRequested = true;
    }
    
    private static synchronized boolean stopRequested() {
        return flags.stopRequested;
    }
}
// flags is an array. Should we use getObjectVolatile/putObjectVolatile to access the element?
public class StopThread {
    private static boolean[] flags = new boolean[n]; 
    
    private static synchronized void requestStop() {
        flags[m] = true;
    }
    
    private static synchronized boolean stopRequested() {
        return flags[m];
    }
}

在第一个示例中,标志是使用 static 初始化程序初始化的。 Java 内存模型保证任何后续读取都将看到引用的更新值,以及 Flags 的正确初始状态(基本上,Flags 将被正确发布)。

但是,由于 Flags 是可变的,并且可能会在以后的某个时间点被多个线程改变,因此您需要使用适当的同步来确保其状态的内存可见性。因此,其字段(或适当的同步)需要 volatile

在第二个示例中,简单地将 flags 声明为 volatile 将无法确保写入数组的内存可见性。它只是确保 happens-before 关系 b/w 写入数组引用并随后从中读取。为确保 happens-before 关系 b/w 写入数组元素并随后从中读取,您需要使用锁定,您已经在这样做了。

为什么这有效? JMM 保证 happens-before 关系 b/w 监视器的释放及其重新获取。当一个线程释放一个稍后被另一个线程获取的锁时,确保一种总排序(由 happens-before 控制)b/w 无论前一个写入发生什么线程以及重新获取锁的线程的任何后续读取。

请记住,将引用声明为 volatile 并不能确保其所引用对象的可变状态的正确可见性。我们仍然需要适当的同步机制来确保内存可见性。