Volatile和AtomicInteger

Volatile

假设有这样一种情况,线程1通过一个flag控制线程2的运行如下图:

Thead Mode.png

如果不对flag做任何处理,那么就会产生可见性问题(Visibility problem),即线程1flag值作出了改变,线程2flag却可能没有改变。
要理解为什么会产生这个问题,先要理解CPU 的缓存是如何构建的。如下图:

CPU Cache.png

假设CPU有两个核心Core1Core2线程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,这是正确的。但是后续的自增操作却出现了问题,线程1value自增后切换到线程2执行,由于线程2已经读取了value,不会再次读取value,那么这里就会出现异常,导致value的值仍然是2。这个问题出现的原因是对value的操作不是原子性的,如何解决这个问题呢?

方法1:

使用synchronized确保每次只有一个线程能够访问value的值并对其进行操作:

Sync1.png

方法2:

使用AtomicInteger,使用AtomicInteger.increment()代替自增操作:

Sync2.png

除了AtomicInteger外还有AtomicBooleanAtomicLong等, 如果是想要使用java.util.concurrent.atomic中不存在的类型,可以使用AtomicReference来实现对所需类型的原子化操作。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。