1.缓存导致的可见性问题
可见性问题是指一个线程修改了某一个共享变量的值时,其他线程是否能够立即知道这个修改。
- 对于串行程序来说,可见性问题是不存在的,因为你在任何一个操作步骤中修改了某个变量,在后续的步骤中读取这个变量的值时,读取的一定是修改后的新值。
- 在并行程序中,如果一个线程修改了某一个全局变量,那么其他线程未必可以马上知道这个改动。多核时代,每颗 CPU 都有自己的缓存,这时 CPU 缓存与内存的数据一致性就没那么容易解决了,一个CPU缓存中的变量对另外一个CPU是不可见的
如上图中,两个线程在执行时,CPU1和CPU2分别将内存中的变量,缓存到CPU的cache或者寄存器中,如果CPU1的线程修改了变量V,那么CPU2的线程可能无法意识到这个改动,依然会读取CPU2中cache或者寄存器中的值,这就产生了可见性问题。
2.解决办法
1.使用volatile关键字
volatile 是禁用CPU缓存的意思,变量volatile int x = 0
,它表达的是:告诉编译器,对这个变量的读写,不能使用 CPU 缓存,必须从内存中读取或者写入。
2.使用synchronized
加锁
JMM(java内存模型)关于synchronized的两条规定:
- 线程解锁前(退出synchronized代码块之前),必须把共享变量的最新值刷新到主内存中,也就是说线程退出synchronized代码块值后,主内存中保存的共享变量的值已经是最新的了
- 线程加锁时(进入synchronized代码块之后),将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁需要是同一把锁)
- 两者结合:线程解锁前对共享变量的修改在下次加锁时对其他线程可见
3.synchronized
和volatile
的比较
1.volatile
不需要加锁,比synchronized
更轻量级,不会阻塞线程
2.从内存可见性角度讲,volatile
读操作=进入synchronized
代码块(加锁),volatile
写操作=退出synchronized
代码块(解锁)
3.synchronized
既能保证可见性,又能保证原子性,而volatile
只能保证可见性,不能保证原子性