多线程并发导致的可见性问题
首先熟悉JMM内存模型,我们可以知道,线程在执行指令的时候,首先从主内存当中拿到共享变量,然后是在自己的工作内存当中执行的,最后再将执行后的结果刷回主内存。
然后,如果有多个线程同时对共享变量进行操作的时候,可能一个线程在执行完指令后,共享变量的副本还没有刷回主内存时,另外一个线程就已经读取了共享变量的值(此时是旧值),然后执行了一堆的代码操作。这样的结果就会导致线程不安全的问题,即共享变量的最终结果不能达到预期值,而是一个并发导致的错误数值。
一个线程对共享变量修改后,另外一个线程并未能及时得到共享变量的新值,就会导致并发过程当中的可见性问题。
多线程+编译器优化导致的有序性问题
编译器为了优化执行效率,可能会对我们编写的代码做一些顺序的调整。这样的调整是不会影响单线程执行结果,即在单线程场景里,调整前的代码执行结果与调整后的代码执行结果是一致的。
但是如果是在多线程的场景下,因为JVM对每一个线程都有单独的程序计数器(用来记录每个线程的指令位置),每个线程执行的指令位置是不一样的,此时可能就会导致有序性的问题。
volidate变量如何解决可见性问题
一旦一个共享变量被volidate修饰,那就会有两种规则。
- 线程对共享变量副本的修改,会立即强制刷新回主内存。
- 此时其他线程的共享变量副本设定为强制无效的,如果要对共享变量进行操作,需要重新从主内存里读取共享变量。
其实内部也是通过内存屏障保证可见性。
volidate内存屏障插入策略中有一条:在每个volatile写操作的后面插入一个StoreLoad屏障。
内存屏障: “StoreLoad屏障”,会生成一个Lock前缀指令,Lock前缀指令在多核处理器下会引发上面两种规则。
volidate变量是如何解决有序性问题
内存屏障。
volidate变量前的代码不能和volidate变量后的代码进行交换。
当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。
当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。
当第一个操作是volatile写时,第二个操作是volidate读时,不能重排序。
volidate内存屏障插入策略
在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个SotreLoad屏障。
在每个volatile读操作后面插入一个LoadLoad屏障。
在每个volatile读操作后面插入一个LoadStore屏障。
Store:数据对其他处理器可见(即:强制刷回主内存)
Load:让缓存中的数据失效,重新从主内存中读取数据。