美文网首页
线程安全----原子类

线程安全----原子类

作者: 任笙_8b8c | 来源:发表于2020-07-23 14:45 被阅读0次
首先我们来看一段代码(模拟抢货):
图片.png

代码解释:下面代码模拟并发抢货过程,我们用了6个线程处理6万的货,按理来说应该是刚刚好库存全部为0.

public class RushOrder {  //库存类
   //我们库存总共6万
   int i =60000;
  public void order(){
          i--;
      }

   //模拟高并发状态下的抢货业务场景
   public static void main(String[] args) throws InterruptedException {
       RushOrder rushOrder =new RushOrder();
       for (int j = 0; j <6; j++) {
       //多个线程 6个线程一块去处理
           new Thread(
                   ()->{ //JDK1.8 的表达式  代表的就是run方法
                       //每个线程走一万次
                       for (int k = 0; k <10000 ; k++) {
                           rushOrder.order();
                       }
                   }
           ).start();
       }
       //休眠5秒
       Thread.sleep(5000);

       System.out.println("当前库存"+rushOrder.i);
   }

}

上面的 代码执行结果很显然并不是我们想要的执行结果
    第一次测试:当前库存20533
    第二次测试:当前库存26500
那这个i--操作在jvm中是怎么走的呢?


mysql.png
图解:

    这里的i--执行了三部操作,那为什么最终的值会不是0呢?原因很简单,因为i--它没有保证原子性

那什么是原子性呢?
  • 一个或多个操作时,要不全部执行,或者在一个线程执行过程中不被任何因素打乱,要不全部不执行.
那为什么i--就不满足原子性呢?(看下图)
mysql.png

图解:

       当线程1拿到i=10的值的时候,执行i--的操作时突然线程二就进来了,抢占了线程的执行权
线程2在共享变量拿到的值也是10,执行i--;然后最终来个线程执行完成后,put上去的值都是9,这就造成了为什么为什么每次执行货不是0的情况.

解决方案

  • 使用原子操作类:
       Atomiclnteger :原子操作类
    更改代码:
  //我们库存总共6万
/*inti=60000;*/
AtomicIntegeri=newAtomicInteger(60000);//相当于i=60000但是它是一个原子操作类
publicvoidorder(){
//i--;
i.decrementAndGet();  //等同于i--它是一个原子性的
}

最终的执行结果为:当前库存0

那为什么decrementAndGet()就能保住线程安全呢?
  • 其实底层原理就是CAS(Compare and swap) 比较和交换.(见下图)


    mysql.png

图解:

  • 比如当前我们的主内存的值是10,我们的线程1来到我们的工作区拿到我们的旧值old=10,
    然后进行CAS比较与主内存的值是否一致,如果不一致的话我们就重新回到get(i),old重新去主内存中拿值,再进行比较 如果这时一比较发现相等,我们就可以吧i-1的值赋给主内存,
    就解决了最后货的值为0的问题.

为什么我们遇到这类原子性问题不去经常选择使用原子操作类,而去使用锁呢?

CAS机制的三大问题:
mysql.png
图解:
  • 1.只能确保一个共享变量的原子性操作
    我们来看调用的方法:
    i.decrementAndGet();//等同于i--它是一个原子性的
    public final int decrementAndGet(){
    return unsafe.getAndAddInt(this,valueOffset,-1)-1;
    }
    如果我们一旦遇到复杂的运算,比如i=i*10/6 就还得每次就定义方法.

  • 2.循环时间长,开销大(这里jvm实现的sync锁会升级为重量级锁)
    每次CAS机制如果都失败了,它必定每次都去重试.
    它是一种乐观机制,它认为总是能匹配对的值,
    但是一直失败会一直匹配一直循环的话会造成cpu占用太长.

  • 3.ABA问题: (脏读,脏取)
    ABA对象, 比如A(未婚),B(已婚)
    比如当我们的老王在放暑假前通过朋友介绍了一个妹子她未婚(A) old=A ,但是老王出去打暑假工了,妹子可能有别的追求者,然后别的线程进来跟她嘿嘿嘿,于是他们就结婚了,主内存的妹子就变成了(B),但是一个月内他们闹矛盾,男的时间太短,离婚了,妹子又单身了,于是呢老王打工回来了,这是CAS匹配主内存发现未婚(A),这时老王就和她结婚了.

相关文章

网友评论

      本文标题:线程安全----原子类

      本文链接:https://www.haomeiwen.com/subject/ncxhlktx.html