Java多线程-06(线程同步中的CAS和原子类)

                                                         线程同步

                                                                                           个人博客:www.xiaobeigua.icu 


1.5 CAS

        CAS(Compare And Swap)是由硬件实现的。

        CAS 可以将 read- modify - write (读存写)这类的操作转换为原子操作。

        i++自增操作包括三个子操作:

                从主内存读取 i 变量值

                 对 i 的值加 1

                再把加 1 之后 的值保存到主内存

1.5.1CAS 原理

         在把数据更新到主内存时,再次读取主内存变量的值,如果现在变量的值与期望的值(操作起始时读取的值)一样就更新,如果不一样,那就有两种情况:

                一种是 重试(自旋)

                一种是什么都不做

自旋原理图:

 

                                  

 

 

1.5.2 CAS中的 A-B-A问题

        CAS 实现原子操作背后有一个假设:  共享变量的当前值与当前线程提供的期望值相同, 就认为这个变量没有被其他线程修改过。

        实际上这种假设不一定总是成立.如有共享变量 count = 0

                A 线程对 count 值修改为 10

                B 线程对 count 值修改为 20

                C 线程对 count 值修改为 0

        当前线程看到 count 变量的值现在是 0,现在是否认为 count 变量的值没有被其他线程更新呢? 这种结果是否能够接受?

         这就是 CAS 中的 ABA 问题,即共享变量经历了 A->B->A 的更新

        是否能够接收 ABA 问题跟实现的算法有关。

        如果想要规避 ABA 问题,可以为共享变量引入一个修订号(时间 戳), 每次修改共享变量时,相应的修订号就会增加 1. ABA 变量更新过程变量: [A,0] ->[B,1]->[A,2], 每次对共享变量的修改都会导致 修订号的增加,通过修订号依然可以准确判断变量是否被其他线程修改过。        

        AtomicStampedReference 类就是基于这种思想产生的。    


1.6 原子变量类

        java 1.5引进原子类,具体在java.util.concurrent.atomic包下,atomic包里面一共提供了13个类,分为4种类型,分别是:原子更新基本类型,原子更新数组,原子更新引用,原子更新属性。原子类也是java实现同步的一套解决方案。

        既然已经有了synchronized关键字和lock,为什么还要引入原子类呢?或者什么场景下使用原子类更好呢?

在很多时候,我们需要的仅仅是一个简单的、高效的、线程安全的递增或者递减方案,这个方案一般需要满足以下要求:

        1、 简单:操作简单,底层实现简单

        2、 高效:占用资源少,操作速度快

        3、 安全:在高并发和多线程环境下要保证数据的正确性

        对于是需要简单的递增或者递减的需求场景,使用synchronized关键字和lock固然可以实现,但代码写的会略显冗余,且性能会有影响,此时用原子类更加方便。

        原子变量类基于CAS实现的, 当对共享变量进行read-modify-write 更新操作时,通过原子变量类可以保障操作的原子性与可见性。对变量 的 read-modify-write 更新操作是指当前操作不是一个简单的赋值,而 是变量的新值依赖变量的旧值,如自增操作i++。

        由于volatile只能保证可见性,无法保障原子性, 原子变量类内部就是借助一个 Volatile 变量, 并且保障了该变量的 read-modify-write 操作的原子性, 有时把原子变量类看作增强的 volatile 变量. 原子变量类有 12 个,如图:

分组

原子变量类
基础数据型AtomicInteger,  AtomicLong,  AtomicBoolean
数组型

AtomicIntegerArray,           

AtomicLongArray,

 AtomicReferenceArray

字段更新器

AtomicIntegerFieldUpdater

AtomicLongFieldUpdater,

AtomicReferenceFieldUpdate

引用型

AtomicReference,

AtomicStampedReference,

AtomicMarkableReference

接下来我们将用代码去感受原子类

1.6.1原子类如何使用

上面介绍了原子类有4大类,这里以原子更新基本类型中的AtomicInteger类为例,介绍通用的API接口和使用方法。

首先是几个常用的API:

// 以原子方式将给定值与当前值相加,可用于线程中的计数使用,(返回更新的值)。
int addAndGet(int delta)

// 以原子方式将给定值与当前值相加,可用于线程中的计数使用,(返回以前的值)
int getAndAdd(int delta)

// 以原子方式将当前值加 1(返回更新的值)
int incrementAndGet()

// 以原子方式将当前值加 1(返回以前的值)
int getAndIncrement() 

// 以原子方式设置为给定值(返回旧值)
int getAndSet(int newValue)

// 以原子方式将当前值减 1(返回更新的值)
int decrementAndGet() :

// 以原子方式将当前值减 1(返回以前的值)
int getAndDecrement()

// 获取当前值
get()

1.6.2 AtomicLongs使用

使用原子变量类定义一个计数器,用来模拟计算网路请求总数和成功数 失败数

计数器类代码:

public class Indicator {
    
    //构造方法私有化
    private Indicator(){}

    //定义一个私有的本类静态的对象
    private static final Indicator INSTANCE = new Indicator();

    //提供一个公共静态方法返回该类唯一实例
    public static Indicator getInstance(){
        return INSTANCE;
    }

    //使用原子变量类保存请求总数,成功数,失败数
    private final AtomicLong requestCount = new AtomicLong(0); //记录请求总数
    private final AtomicLong successCount = new AtomicLong(0); //处理成功总数
    private final AtomicLong fialureCount = new AtomicLong(0); //处理失败总数

    //有新的请求
    public void newRequestReceive(){
        requestCount.incrementAndGet();
    }

    //处理成功
    public void requestProcessSuccess(){
        successCount.incrementAndGet();
    }

    //处理失败
    public void requestProcessFailure(){
        fialureCount.incrementAndGet();
    }

    //查看总数,成功数,失败数
    public long getRequestCount(){
        return requestCount.get();
    }

    public long getSuccessCount(){
        return successCount.get();
    
    }
    public long getFailureCount(){
        return fialureCount.get();
    }
}

测试代码:

public class Test {
public static void main(String[] args) {

    for (int i = 0; i < 10000; i++) {
        new Thread(new Runnable() {

            @Override
            public void run() {

                    //每个线程就是一个请求,请求总数要加 1
                Indicator.getInstance().newRequestReceive();
                int num = new Random().nextInt();
            
                if ( num % 2 == 0 ){ //偶数模拟成功
                    Indicator.getInstance().requestProcessSuccess();
                }else { //处理失败
                    Indicator.getInstance().requestProcessFailure();
                }
            }
        }).start();
    }


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

    //打印结果
    System.out.println( Indicator.getInstance().getRequestCount()); //总的请求数
    System.out.println( Indicator.getInstance().getSuccessCount()); //成功数
    System.out.println( Indicator.getInstance().getFailureCount()); //失败数
    }
}

1.6.2 AtomicIntegerArray的基本使用

        原子更新数组的常用操作

public class Test {
    public static void main(String[] args) {

        //1)创建一个指定长度的原子数组
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
        System.out.println( atomicIntegerArray ); //[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    
        //2)返回指定位置的元素
        System.out.println( atomicIntegerArray.get(0)); //0
        System.out.println( atomicIntegerArray.get(1)); //0

        //3)设置指定位置的元素
        atomicIntegerArray.set(0, 10);

        //在设置数组元素的新值时, 同时返回数组元素的旧值
        System.out.println( atomicIntegerArray.getAndSet(1, 11) ); //0
        System.out.println( atomicIntegerArray ); //[10, 11, 0, 0, 0, 0, 0, 0, 0, 0]
   
        //4)修改数组元素的值,把数组元素加上某个值
        System.out.println( atomicIntegerArray.addAndGet(0, 22) ); //32
        System.out.println( atomicIntegerArray.getAndAdd(1, 33)); //11
        System.out.println( atomicIntegerArray ); //[32, 44, 0, 0, 0, 0, 0, 0, 0, 0]
        //5)CAS 操作

        //如果数组中索引值为 0 的元素的值是 32 , 就修改为 222
        System.out.println( atomicIntegerArray.compareAndSet(0, 32, 222)); //true
        System.out.println( atomicIntegerArray ); //[222, 44, 0, 0, 0, 0, 0, 0, 0, 0]
        System.out.println( atomicIntegerArray.compareAndSet(1, 11, 333)); //false
        System.out.println(atomicIntegerArray);

        //6)自增/自减
        System.out.println( atomicIntegerArray.incrementAndGet(0) ); //223, 相当于前缀
        System.out.println( atomicIntegerArray.getAndIncrement(1)); //44, 相当于后缀
        System.out.println( atomicIntegerArray ); //[223, 45, 0, 0, 0, 0, 0, 0, 0, 0]
        System.out.println( atomicIntegerArray.decrementAndGet(2)); //-1
        System.out.println( atomicIntegerArray); //[223, 45, -1, 0, 0, 0, 0, 0, 0, 0]
        System.out.println( atomicIntegerArray.getAndDecrement(3)); //0
        System.out.println( atomicIntegerArray ); //[223, 45, -1, -1, 0, 0, 0, 0, 0, 0]

    }
}
      

还有一些原子类这里就不多做演示了,感兴趣的可以去面向互联网编程找一下哈哈哈

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值