倘若synchronize只能用于实现原子性或确定临界区(Critical Section)。那么线程A修改了某个共享参数后,线程B可能获取到的参数不是最新的,同样会出现问题。因此,同步还有另外一个重要方面:内存可见性(Memory Visibility)。
为了保证多个线程间对内存写入操作的可见性,必须使用同步机制。
1、失效数据:共享数据在多个线程下,缺乏同步时,2个共享数据可能出现4种不同的复杂情况。但是,这个失效值是由之前某个线程设置的值,而不是一个随机值。这种称为最低安全性(out-of-thin-airsafety)。
2、非原子的64位操作:最低安全性适用于大多数变量。但是非volatile的64位数值变量(double和long)。Java内存模型要求,变量的读取和写入操作必须是原子操作,但对于非volatile类型的long和double变量,JVM会分解为两个32位的操作。针对64位变量的读写操作在不同线程中执行,那么就可能读取到某个值得高32位,另一个值得低32位。
因此,针对共享的和可变的状态操作时,需要使用同步机制保护。
3、加锁和可见性:核心是锁的happends-before原则,同一个锁的unlock操作happend-before锁的lock操作。例:A、B线程先后进入由同一个锁保护的同步代码块时,当B执行由锁保护的同步代码块时,可以看到A之前在同一个代码块中的所有操作结果。注意单例中的DCL。
4、volatile和可见性:核心是volatile的happends-before原则,对一个volatile变量的写操作happend-before此变量的任意操作(包括写操作)。例:线程A、B依次写入和读取volatile变量,在写入volatile变量之前对A可见的所有变量的值,在B读取volatile变量后,对B都是可见的。因此,从内存可见性角度来看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。
因此,加锁机制确保可见性和原子性,而volatile变量只能确保可见性。