Java-多线程

Volatile和AtomicInteger

2020-10-29  本文已影响0人  From64KB

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来实现对所需类型的原子化操作。
上一篇 下一篇

猜你喜欢

热点阅读