Volatile
假设有这样一种情况,线程1
通过一个flag
控制线程2
的运行如下图:
Thead Mode.png
如果不对
flag
做任何处理,那么就会产生可见性问题(Visibility problem
),即线程1
对flag
值作出了改变,线程2
的 flag
却可能没有改变。要理解为什么会产生这个问题,先要理解CPU 的缓存是如何构建的。如下图:
CPU Cache.png
假设CPU有两个核心
Core1
和Core2
,线程1
和线程2
分别在两个核心上运行。每个核心都有自己的local cache
和公共的shared cache
,线程1
和线程2
都使用了flag
,那么分别会在自己的local cache
中加载flag
。这时如果线程1
改变了flag
,这时对于线程2
来说local cache
中的flag
仍然是之前的值,这就会造成异常。这也就是可见性问题(Visibility problem
)。解决的这个问题的方法很简单,给
flag
加上关键字volatile
即可。为什么这样就可以解决可见性问题呢?Volatile.png
加上
volatile
后,每次改变flag
的值都会同时写入(flush
)到shared cache
中,并且刷新(refresh
)其他local cache
中的flag
值。这样就可以解决可见性问题(Visibility problem
)。
AtomicInteger
接下来看另外一种情况,两个线程,同时对一个值做自增操作,如下图:
AtomicInteger.png
通过
volatile
中的例子,我们可以确定,这样做是不行的。那么给value
加上volatile
是不是就可以了呢?如果你做足够多的尝试的话,会发现仍然是不行的。因为这不仅仅是一个可见性问题,这里还包含了同步问题。先让我们看下面这张图:Sync.png
图中的
# 1、2、3、4
表示了CPU的某个可能的执行顺序。对于value
而言加上volatile
后,两个线程读取到的都是1,这是正确的。但是后续的自增操作却出现了问题,线程1
对value
自增后切换到线程2
执行,由于线程2
已经读取了value
,不会再次读取value
,那么这里就会出现异常,导致value
的值仍然是2。这个问题出现的原因是对value
的操作不是原子性的,如何解决这个问题呢?
方法1:
使用synchronized
确保每次只有一个线程能够访问value
的值并对其进行操作:
Sync1.png
方法2:
使用AtomicInteger
,使用AtomicInteger.increment()
代替自增操作:
Sync2.png
除了
AtomicInteger
外还有AtomicBoolean
、AtomicLong
等, 如果是想要使用java.util.concurrent.atomic
中不存在的类型,可以使用AtomicReference
来实现对所需类型的原子化操作。