原子变量&&CAS算法

来源:互联网 发布:淘宝网薄印花半大开衫 编辑:程序博客网 时间:2024/05/17 04:33

进入今天的主题先看一段代码和代码的输出结果

class AtomicDemo implements Runnable{    private int serialNumber=0;    public int getSerialNumber() {        return serialNumber++;    }    @Override    public void run() {        try {            Thread.sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(getSerialNumber());    }}public class TestAutomic {    public static void main(String[] args) {        AtomicDemo atomicDemo=new AtomicDemo();        for (int i = 0; i < 10; i++) {            new Thread(atomicDemo).start();         }    }}

这里写图片描述

可以看到在多线程的情况下serialNumber++操作出现了问题,分析下为什么会出现这个问题?

这里写图片描述

主内存和工作内存的区别:
根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。

由于serialNumber++操作,会在内存中产生一个临时的变量temp,首先将temp=serialNumber,然后serialNumber=serialNumber+1操作,最后serialNumber=1去更新主存的值;但是在多线程并发的情况下,线程2拿到的serialNumber的值可能是0,可能不是serialNumber=serialNumber+1操作后的值。由于原子性的操作是不可以分割的,但是++的操作把serialNumber++分割了,在多线程的情况下,所以出现了这种情况。在Volatile关键字内存可见的情况下,输出的结果还是一样的,因为Volatile关键字不能保证变量状态的“原子性操作”,怎么解决这个问题,先看2个概念。

CAS算法:

CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器 操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并 发访问。
CAS 是一种无锁的非阻塞算法的实现。
CAS 包含了 3 个操作数:
需要读写的内存值 V
进行比较的值 A
拟写入的新值 B
当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的 值,否则不会执行任何操作。

原子变量:

类的小工具包,支持在单个变量上解除锁的线程安全编程。事实上,此包中的类可将 volatile 值、字段和数组元素的概念扩展到那些也提供原子条件更新操作的类。
类 AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的实例各自提供对 相应类型单个变量的访问和更新。每个类也为该类型提供适当的实用工具方法。
AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray类进一步扩展了原子操 作,对这些类型的数组提供了支持。这些类在为其数组元素提供 volatile 访问语义方 面也引人注目,这对于普通数组来说是不受支持的。
核心方法:boolean compareAndSet(expectedValue, updateValue)
java.util.concurrent.atomic包下提供了一些原子操作的常用类:
AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference  AtomicIntegerArray、AtomicLongArray
AtomicMarkableReference
AtomicReferenceArray
AtomicStampedReference

看上图的下方的左边部分,V=0 拿到是主存的值,A=0是作为比较的值,B=1是在当前的线程1,V+1之后之后的值(通过B来更新主存的值),右边部分 V=0 在多线程的情况下拿到的可能是0,A=1是在线程1(多线程的情况下),+1之后改之后的值,B=1是当前的线程2,V+1之后的值,V!=A,此时不能拿B去更新主存的值,说白了就是在多个线程的情况下,只有一个线程会执行成功。但是由于CAS 是一种无锁的非阻塞算法的实现,压根不会放弃CPU给它的执行权,执行的效率非常的高,又会去执行一遍,取主存拿值,然后在计算更新值。这个要清楚的是CAS算法是一种硬件对并发的支持(看概念)。

修改后的代码

class AtomicDemo implements Runnable{    //private int serialNumber=0;    private AtomicInteger atomicInteger=new AtomicInteger();    public int getSerialNumber() {        return atomicInteger.getAndIncrement();  //自增加1    }    @Override    public void run() {        try {            Thread.sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(getSerialNumber());    }}public class TestAutomic {    public static void main(String[] args) {        AtomicDemo atomicDemo=new AtomicDemo();        for (int i = 0; i < 10; i++) {            new Thread(atomicDemo).start();         }    }}

原子变量保证了数据的原子性,volatile关键字内存可见性。

说到最后,到底CAS算法是怎么实现的? 下篇博客介绍。

0 0
原创粉丝点击