AtomicInteger类多次调用不能保证原子性

AtomicInteger类多次调用不能保证原子性

AtomicInteger是JUC下并发包的一个原子操作类,那就不得不提到原子性这个概念了

原子性

指的就是这个区间内是不可分割的,保证了这个区间内的执行语句是一个整体。(个人理解)
原⼦(atomic)本意是“不能被进⼀步分割的最⼩粒⼦”,⽽原⼦操
作(atomic operation)意为“不可被中断的⼀个或⼀系列操作”。<摘自《java并发编程的艺术》>

AtomicInteger的原子性

在AtomicInteger中怎么保证了原子性的呢,AtomicInteger底层采用了Unsafe中调用 compareAndSwapInt 方法来实现CAS操作从而保证了原子性。

CAS(无锁/自旋锁)

JVM中的CAS操作时利用了处理器提供的CMPXCHG指令实现的。自旋CAS实现的基本思路就是无限循环进行CAS操作直到成功为止。
CAS实现思路很好理解,这里简单阐述一下
还是通过经典例子i++这个操作来说
i++这个操作不是一步操作,编译后至少是三步操作(在汇编层)
会转换成 i=0;a=i+1;i=a;大致这样的操作,其中a是一个中间存储值。
假如两个线程同时访问i++;这个操作,就会出现以下的现象。
在这里插入图片描述我们进行两次i++操作,期望值是3,但是也会出现上述现象结果是2。
原因可能是多个cpu同时从各自的缓存中取出这个值,同时执行了写操作,然后分别写入系统内存中。在这个要想保证这个值是3 的情况,就是线程1在执行写操作的时候.线程2不能进行任何操作。这不就是所谓的锁了?
而CAS则是不加锁的操作,通过比对当前值跟期望值是否一致,一致就改变新值,如果不一致就在循环里面一直循环,直到取出的当前值跟期望值一致操作成功之后才会退出死循环(自旋)
代码演示<摘自《java并发编程的艺术》>

/**
* 模拟CAS实现过程
* @author Xuzs
*/
public class Atomic_CAS {
   private AtomicInteger atomicInteger=new AtomicInteger ();
   private int i=0;
   /**
    * 线程安全的计数器
    */
   public void safeCount(){
       for (;;){
           //取出值原子性操作
           int i=atomicInteger.get ();
           //比较与赋值也是原子性操作,指令级别保证原子性
           boolean b = atomicInteger.compareAndSet (i, ++i);
           //赋值成功退出循环
           if (b){
               break;
           }
       }
   }
   /**
    * 非线程安全的计数器
    */
   public void count(){
       i++;
   }

   public static void main(String[] args) {
      final Atomic_CAS a=new Atomic_CAS ();
      //创建list集合来模拟多线程并发的效果
       List<Thread> list=new ArrayList <> ();

       long start = System.currentTimeMillis();
       //添加100线程
       for (int i=0;i<100;i++){
           list.add (new Thread (()->{
               //通过for实现递增效果
               for (int j=0;j<1000;j++){
                   a.safeCount ();
                   a.count ();
               }
           }));
       }
       list.forEach (o->o.start ());
       //等待所有线程执行完
       list.forEach (o->{
           try {
               o.join ();
           } catch (InterruptedException e) {
               e.printStackTrace ( );
           }
       });
       System.out.println ("线程不安全====="+a.i);
       System.out.println ("线程安全====="+a.atomicInteger);
       System.out.println(System.currentTimeMillis() - start);
   }
}
//打印效果
线程不安全=====99183
线程安全=====100000
168

通过上面的例子我们可以知道,CAS虽然可以保证原子性但是同时也是存在问题的

CAS存在的问题

ABA问题

理论上概念描述:因为CAS在操作值时,会检查值有没有发生变化,如果没有变化就给他更新值,但是如果一个值时A,在调用之前,已经被其他线程改成了B,之后又改成了A,那么当你调用的时候你检查他的值并没有发生变化还是A,之后你就给他更新了,但实际他已经不是原来的值了。
粗糙的描述(简单粗暴,通俗易懂):假如你跟你的对象分手了,然后你的对象在你分手之后又找了一个伴侣,之后他们甜蜜的生活了一段时间之后分开了,这时候你又浪子回头,来找你的对象复合,你对像也同意了,你们又幸福的在一起了,但是你不知道你对象在你们分开之后跟其他人好上了。这不就是ABA问题了?(前提是你有对象!)
怎么处理ABA问题呢?
可以通过版本号来处理,每次修改都给他加一个版本号,在你更新值时对比一下版本号是否一致,一致才修改,不一致重新循环取值对比。

循环时间长开销⼤

CAS是通过无限循环来保证原子性的,for循环是占用cpu资源的,因为无限循环是不会放弃cpu执行权的,循环的次数越多对cpu的消耗就越大。
所以CAS操作不适合在并发量高的情况下使用,因为自旋会占用cpu的资源
怎么处理这个问题,可以通过指令来调优(对指令不是很清楚,就不献丑了)

只能保证⼀个共享变量的原⼦操作

终于来到这篇文章的重点了,为什么CAS只能保证一个共享变量的原子操作呢?当对⼀个共享变量执⾏操作时,我们可以使⽤循环CAS的⽅式来保证原⼦操作,但是对多个共享变量操作时,循环CAS就⽆法保证操作的原⼦性,这个时候就可以⽤锁。
因为你不能保证你两次调用的CPU指令集是连续的
我写一个程序来模拟这个问题,字面意思很难看得懂,这个问题我也是查了很多资料,问了很多大佬,大佬都被我蠢哭了,我才明白的~~

/**
 * 复现AtomicXXX类在多次调用时不能保证原子性
 * @author Xuzs
 */
public class AtomicIntegersTest2 {
    static volatile boolean flag=true;
    public static void main(String[] args) {
        AtomicInteger a=new AtomicInteger();
        new Thread(()->{
            while (flag)
                a.incrementAndGet();
        }).start();

        new Thread(()->{
            int a1=  a.incrementAndGet();
            //cpu 会调度,切换其他线程进来
            //因为你不能保证这两行代码是一个整体的操作,
            // 所以AtomicInteger在多次调用的时候不能保证原子性
            //执行间隙
            int a2=a.incrementAndGet();
            System.out.println(a2-a1);
            flag=false;
        }).start();
    }
}

以上是我对AtomicInteger的浅解,说的不对地方欢迎评论区指正!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值