JUC之二 原子变量与CAS算法

一、原子性

       概念:不可分割的

原子性的一个例子:a=5;—-赋值,只涉及到写入工作内存中

简单的理解:只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作

i++的问题

       分析:i++在计算机的底层完成的步骤:读、改、写

       过程:int temp=i;读———-→(2)i=i+1;改———-→(3)i=temp;写入到主存中

例1  

package www.wzj.juc;

/*
 * 一、i++ 的原子性问题:i++ 的操作实际上分为三个步骤“读-改-写”
 * 		  int i = 10;
 * 		  i = i++; //10
 *
 * 		  int temp = i;
 * 		  i = i + 1;
 * 		  i = temp;
 *
 * 二、原子变量:在 java.util.concurrent.atomic 包下提供了一些原子变量。
 * 		1. volatile 保证内存可见性
 * 		2. CAS(Compare-And-Swap) 算法保证数据变量的原子性
 * 			CAS 算法是硬件对于并发操作的支持
 * 			CAS 包含了三个操作数:
 * 			①内存值  V
 * 			②预估值  A
 * 			③更新值  B
 * 			当且仅当 V == A 时, V = B; 否则,不会执行任何操作。
 */
public class TestAtomicDemo {

    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();

        for (int i = 0; i < 10; i++) {
            //十个线程同时运行
            new Thread(ad).start();
        }
    }

}

class AtomicDemo implements Runnable{

	private  int serialNumber = 0;
   //	private volatile int serialNumber = 0;//说明
   // private AtomicInteger serialNumber = new AtomicInteger(0);

    @Override
    public void run() {

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
        }

        System.out.println(getSerialNumber());
    }

    public int getSerialNumber(){

        return serialNumber++;// 0、1、2、2、3、4、5、6、7、8、9
       // return serialNumber.compareAndSet();//
    }

}

      分析过程:共享数据在内存中的操作过程。主存是”serialNumber=0″,线程1把主存中的数据读取到缓存中,也即”int emp=serialNumber”;然后完成serialNumber=serialNumber+1;此时第三步:准备写入的时候(i=temp),准备把缓存中的数据写入到内存中,但是线程2抢占了CPU的执行权,也从主存中去读取数据,把”serialNumber=0″的值从主存中读过来(线程1还没有写入),此时线程2也完成上面的工作,先读过来”int temp=serialNumber”再运算”serialNumber=serialNumber+1″,但是线程1又抢到了,完成了写入内存的操作,然后打印,再接着线程2也写入主存中,也打印了,会发现二者的数据一样

     思考:好像valiate关键字都能解决上述的问题,真的吗?

     分析:如果volatile关键字修饰变量,内存具有可见性.上一篇我们谈到,volatile修饰的变量可以理解为在主存中的修改,此时

线程1:”int temp=serialNumber”以及”serialNumber=serialNumber+1″是在主存中完成的

线程2:”int temp=serialNumber”以及”serialNumber=serialNumber+1″也是在主存中完成的

问题来了:如果volatile修饰,仍然要完成仍然要完成三步操作,由于volatile不具有互斥性,多个线程可以对其进行更改。如果

仅用valiate修饰,无非是意味着当线程1准备写1的时候,线程2也读到了,也准备写1,这个问题依然存在。

总结:volatile只能保证内存可见性问题,不能保证原子性问题

再次分析:按照我们的需求”serialNumber++”明明是不可分割的,但是现在分割开来了

解决:原子变量

jdk1.5以后,提供了java.util.concurrent.atomic

常见的原子类:AtomicBoolean、AtomicInteger、AtomicLong、AtomicIntegerByteArray

特点:类里面封装的变量都是voliate修饰,保证内存可见性,那么如何保证原子性呢?利用CAS算法保证数据的原子性

二、CAS算法

概述:是硬件(底层)对于并发操作共享数据的支持,JVM对其也支持。

原理:包含三个操作,内存值  V、预估值  A、更新值  B,当且仅当 V==A时,V=B,否则将不做任何操作

以上面的例子为例,具体的判断过程

(1)线程1首先会从主存中读取数据 V=0;

(2)在替换的时候(往主存中写入数据的时候)会去”读取”原来的旧值;(看是否有其它线程,趁其不注意修改了主存中的共享变量)

(3)如果读取到的旧值:A=0(表明没有其他的线程对其进行修改)

(4)此时进行运算,会把最终的值写入到主存中

理解为两步

(1)读取内存中是第一步

(2)比较和替换(写入)是第二步(此步同步的,一次只有一个线程能进来)

CAS算法如何处理例1出现的问题:线程2抢到了CPU执行权先”读取”主存中也是1(线程1还没来得及写入),然后线程1完成了写

入,然后线程2从内存中读取原来的旧值(其实已经发生变化)–预估值,发现serialNumber=2,发现与原来读取的数据不一样,

而要改的值也是2,此时什么也不做。

保证了:多个线程并发的要对主存中的数据修改的时候,有且只有一个线程会成功

CAS比原来的synchronized效率高的原因

原因:是此次不成功,但是不会处于阻塞的状态,即不会放弃CPU的执行权,可以立即尝试再去更新,所以CAS算法要比同步锁

的效率高

缺点:自己要写的步骤比较多(比如说一旦失败了怎么做)

随后:模拟CAS算法

AtomicInteger类中

常用的方法

(1)getAndIncrement—先获取,再递增(减略)–i++

(2)getAndSet———–先获取,再设置

(3)IncrementAndGet—先递增再获取—++i

(4)compareAndSet  —-先比较再替换(例子中获取的)

相关资料:
点击打开链接

    原文作者:JUC
    原文地址: https://blog.csdn.net/wzj_110/article/details/80160895
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞