关于Java中的Atomic

为什么会出现Atomic类
在多线程或者并发环境中,我们常常会遇到这种情况 int i=0; i++ 但这种写法是线程不安全的。
为了达到线程安全的目的,我们通常会用synchronized来修饰对应的代码块。还有一种办法就是使用J.U.C包下的atomic类。
Atomic类的原理是什么呢
atomic类是通过自旋CAS操作volatile变量实现的。
CAS(O,V,A)
V:主内存存放的实际变量值
O:当前线程认为主内存的变量值
A:希望将变量替换的值
主内存:num= 1;—>经过线程一变成0
线程1: num= 0; cas(1,1,0) O=V,认为此时没有线程修改主内存值,此时将0,更新到主内存
线程2: num= 0; cas(1,0,0) O!=V,认为已经有线程修改了这个值,此时修改失败,返回主内存的最新值O,再次重试

使用volatile修饰变量是为了多个线程间变量的值能及时同步。也就是所谓的可见性,是指当一条线程修改了共享变量的值,新值对于其他线程来说是可以立即得知的。

为什么使用Atomic类
因为volatile虽然比synchronized性能要好一些,但是由于例如i++这种操作不符合原子性,而是个复合操作。我们可以简单讲这个操作理解为由这三步组成:

1.读取

2.加一

3.赋值
所以,在多线程环境下,有可能有线程将num读取到本地内存中,此时其他线程可能已经将num增大了很多,当前线程依然对过期的num进行自加,重新写到主存中,最终导致了num的结果不合预期。
所以此时可以使用java并发包中的原子操作类原子操作类是通过循环CAS的方式来保证其原子性的。

ABA问题:
 对于一个旧的变量值A,线程2将A的值改成B又改成可A,此时线程1通过CAS看到A并没有变化,但实际A已经发生了变化,这就是ABA问题。解决这个问题的方法很简单,记录一下变量的版本就可以了,在变量的值发生变化时对应的版本也做出相应的变化,然后CAS操作时比较一下版本就知道变量有没有发生变化。
atomic包下AtomicStampedReference类实现了这种思路。Mysql中Innodb的多版本并发锁也是这个原理。
CAS机制会产生ABA问题:
num = 0;—> 1—>0(线程1线程2串行,线程1线程3并行)
线程1:cas(0,0,1) 1替换成主内存
线程2:cas(1,1,0) 1替换成了0
线程3:cas(0,0,5) 0替换成了5
产生了线程二的数据脏读,避免思路:可以添加一个版本号,即修改的次数,判断当前值到底有没有被改

你可能感兴趣的:(java)