可见性它是一个线程对主内存的修改可以及时的被其他线程观察到。有了Java内存模型的基础,这三个原因就很容易理解了。对于可见性,JVM提供了synchronized和volatile,首先我们来看一下synchronized。
Java内存模型关于synchronized有两条规定,我们在原子性里面,synchronized做演示时,我们介绍了它的四种修饰方法,修饰方法的前两条是针对于调用对象的,对不同的对象它们其实的锁的范围其实是不一样的,这个时候如果不是同一把锁它们互相之间是不影响的。正因为有了synchronized的可见性,以及我们之前介绍的原子性,因此我们在做线程安全同步的时候,我们只要使用synchronized进行修饰之后,我们的变量可以放心地进行使用。
volatile它是通过内存屏障和禁止重排序优化来实现可见性的。volatile变量在每次被线程访问时,都强迫从主内存中读取该变量的值,而当该变量发生变化的时候,又会强迫线程将最新的值刷新到主内存,这样的话任何时候不同的线程总能看到该变量的最新值。volatile读操作和写操作,插入内存屏障、禁止重排序的示意图。
volatile写操作,插入store屏障的示意图。在遇到volatile写操作时,首先会在volatile写之前插入一个StoreStore的屏障,它的作用是禁止上面的普通写和下面的volatile写进行重排序,之后呢会在volatile写后面插入一个StoreLoad的屏障,它的作用是防止上面的volatile写与下面可能有的volatile读或者写进行重排序。
接下来看一下volatile读。当遇到volatile读的时候,会插入Load的屏障。具体呢是首先插入一个LoadLoad屏障,LoadLoad屏障它的作用是禁止下面所有的读操作和上面的volatile读进行重排序。接下来会插入的是LoadStore屏障,LoadStore屏障的作用它是禁止下面所有的写操作和上面的volatile读发生重排序。所有这些都是在CPU指令级别进行操作的。因此当我们使用volatile的时候,它已经具备了当前我们所有说的这些规范,插入了这么多的屏障来保证线程的可见性。这时候许多同学可能就会问,如果我使用volatile去修饰我之前计数器的值,是否它能也出来一个线程安全的结果呢?我们去实际操作一下看看结果是多少。