有如下静态成员变量num,初始值为0;有两个线程,一个是main主线程,另一个是子线程,让子线程休眠2秒,触发主线程的死循环,2秒后,子线程开始修改值为100,此时主线程的死循环仍未停止,由此可证,线程之间不可见。
在num前加上volatile关键字之后,主线程的死循环立刻停止,由此可知,volatile具有保证内存可见的特性。
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排,一般分为以下三种:源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系统的重排 -> 最终执行指令
单线程环境里面确保最终执行结果和代码顺序的结果一致
处理器在进行重排序时,必须要考虑指令之间的数据依赖性
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。
按照正常单线程环境,执行顺序是 1 2 3 4
但是在多线程环境下,可能出现以下的顺序:
2 1 3 4
1 3 2 4
上述的过程就可以当做是指令的重排,即内部执行顺序,和我们的代码顺序不一样
但是指令重排也是有限制的,即不会出现下面的顺序
4 3 2 1
因为处理器在进行重排时候,必须考虑到指令之间的数据依赖性
因为步骤 4:需要依赖于 y的申明,以及x的申明,故因为存在数据依赖,无法首先执行
Volatile实现禁止指令重排优化,从而避免了多线程环境下程序出现乱序执行的现象
首先了解一个概念,内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,它的作用有两个:
由于编译器和处理器都能执行指令重排的优化,如果在指令键插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说,通过插入内存屏障前后的指令执行重排序优化。
内存屏障另外一个作用是刷新出各种CPU的缓存数,因此任何cpu上的线程都能读取到这些数据的最新版本,也就是在Volatile的写和读的时候,加入屏障,防止出现指令重排
工作内存与主内存同步延迟现象导致的可见性问题,可以使用synchronized或volatile关键字解决,它们都可以使得一个线程修改后的变量立即对其他线程可见。
对于指令重排导致的可见性问题和有序性问题,可以利用volatile关键字解决,因为volatile的另一个作用就是禁止重排序优化。
看看,对于i++的操作是非原子性的,所以两个线程去++的时候,会出现数据错乱,而且加了volatile关键字也没用。
那么有什么办法能保证递增这样的原子操作呢?原子类,比如AtomicInteger,内部调用本地方法,用cpp实现原子操作。
下一篇:2.1 Redis中SDS的定义