前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >并发编程之CAS算法与原子变量详解

并发编程之CAS算法与原子变量详解

原创
作者头像
小明爱吃火锅
发布2023-11-03 09:42:28
4020
发布2023-11-03 09:42:28
举报
文章被收录于专栏:小明说Java

前言

在并发编程中,CAS算法和原子变量是实现并发控制的关键技术之一。本文将详细介绍CAS算法和原子变量的原理、使用方法和注意事项,包括它们的优点、缺点和适用范围。同时,本文还将利用代码案例介绍如何使用CAS算法和原子变量来解决并发问题。

一、什么是CAS算法

CAS(Compare And Swap)算法是一种乐观锁技术,用于实现多线程并发控制。CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。CAS在硬件层面保证了比较并交换操作的原子性。

CAS算法的优点在于它是一种无锁算法,可以在不使用锁的情况下实现并发控制。相比于传统的锁机制,CAS算法具有更高的并发性和性能。因为传统的锁机制需要将整个操作过程加锁,等待所有线程都完成后再释放锁,而CAS算法则可以在不使用锁的情况下实现并发控制。简单来说:

CAS算法是硬件对于并发操作共享数据的支持,一种无锁非阻塞算法的实现,乐观锁算法。

CAS 包含了三个操作数

内存值 V

预估值 A

更新值 B

当且仅当 V == A 时,V=B,否则不做任何操作。

在java.util.concurrent.atomic包下面就是利用CAS算法保证变量原子性,通过查看代码CAS依靠的原生类Unsafe,存在sun.misc包,类方法用native修饰。这说明CAS是CPU指令级,只要我们安装了jdk,就实现,没法改变的。

二、CAS算法使用场景和缺点

1.CAS算法使用场景

  • 共享变量叠加的时候,比如volatile修饰解决不了原子问题
  • 批量插入进行排序时
  • lambda表达式里面 + 操作时(其实算是Lambda表达式下访问外部变量),否则编译报错

2.CAS算法缺点

  • ABA问题:一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。这可能会导致一些并发问题,比如死锁和活锁。为了解决这个问题,可以使用带有版本号的CAS操作,即每次更新值时增加版本号,从而保证更新的正确性。
  • 自旋操作:如果CAS操作失败,那么线程会不断地进行自旋操作,等待内存位置的值发生变化。这会浪费大量的CPU资源。为了解决这个问题,可以使用忙等待或者阻塞等待的方式,即在自旋操作时加入适当的延迟或者阻塞等待条件。
  • 顺序问题:CAS操作只能保证单个内存位置的原子性,不能保证多个内存位置的原子性。因此,在进行多个CAS操作时,需要注意操作的顺序和依赖关系,以避免出现数据竞争和死锁等问题。

主要还是ABA问题,这个后面有专讲分析,并如何解决。

三、原子变量

原子变量是一种可以在不使用锁的情况下实现并发控制的数据结构。它提供了一些原子操作,比如原子加法、原子减法、原子取反等,这些操作都是不可分割的,即不会被其他线程打断。这使得原子变量可以在多线程环境下安全地使用。

Java中的java.util.concurrent.atomic包提供了许多原子变量类,比如AtomicInteger、AtomicLong、AtomicBoolean等。这些类中的方法都是用硬件级别的指令实现的,可以在不使用锁的情况下保证原子性。

1.变量的原子性问题

我们都知道,在多线程环境下,使用volatile对共享变量进行叠加i++操作,会出现结果跟实际结果不一致,i++实际上分为“读-改-写”,实际是有这样的:

代码语言:javascript
复制
int temp = i;
i = i+1;
i = temp;

这样多线程同时操作,就有可能出现意想不到的结果。这种在多线程环境下,多个线程同时对同一个变量进行读写操作,可能会导致数据的不一致性,就是变量的原子性问题。

为了解决变量的原子性问题,可以采用以下几种方法:

  • 使用synchronized关键字进行同步。通过在变量周围添加synchronized关键字,可以保证同一时间只有一个线程可以访问该变量,从而避免多个线程同时对该变量进行操作。
  • 使用Lock锁进行同步。Lock锁是一种更细粒度的同步机制,可以在代码块的开头和结尾分别获取锁和释放锁,从而保证同一时间只有一个线程可以访问该变量。
  • 使用Atomic类进行操作。Java提供了Atomic类,如AtomicInteger、AtomicLong等,这些类中的方法都是用硬件级别的指令实现的,可以在不使用锁的情况下保证原子性。
  • 使用CAS操作。CAS(Compare And Swap)是一种无锁算法,可以在不使用锁的情况下实现并发控制。CAS操作通过比较并交换内存中的值和CPU中的值,来保证操作的原子性。

2.Atomic 保证原子性

为了保证变量原子性,使用加锁,肯定是可以解决,加锁使得每次只允许一个线程进行读写操作,但是无疑会减低程序性能,所以本次以Java中的java.util.concurrent.atomic包提供了许多原子变量类,比如AtomicInteger、AtomicLong、AtomicBoolean等。

在volatile多线程叠加的时候,继续模拟20个线程,每个线程叠加到100,我们修改变量为AtomicInteger类型,具体代码如下:

代码语言:javascript
复制
class MyData{
    volatile int number = 0;
    public void addTo60(){
        this.number = 60;
    }
    //使用automic类
    AtomicInteger atomicInteger = new AtomicInteger();
    public void addAtomic(){
        atomicInteger.getAndIncrement();
    }
}

public class VolatileDemo {

    public static void main(String[] args) {
        MyData myData = new MyData();
        for (int i = 0; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 100; j++) {
                    myData.addPlusPlus();
                    myData.addAtomic();
                }
            },String.valueOf(i)).start();
        }

        // 需要等待上面所有子线层执行完毕之后,主线程在运行
        // 如果当前线程大于2,说明还有子线程
        while(Thread.activeCount() > 2){
            Thread.yield();
        }
        System.out.println("volatile修饰结果:"+myData.number);
        System.out.println("原子类AtomicInteger 结果:"+myData.atomicInteger);

    }
}

运行结果:可以看到原子类AtomicInteger修饰的结果,才是正确的。

总结

其实并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题,通过这讲以及之前讲解的volatile变量,我们基本保证并发环境的原子性、可见性和有序性。解决的方式有很多种,也各只有优缺点,在实际的开发中,我们需要根据业务场景选择。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、什么是CAS算法
  • 二、CAS算法使用场景和缺点
    • 1.CAS算法使用场景
      • 2.CAS算法缺点
      • 三、原子变量
        • 1.变量的原子性问题
          • 2.Atomic 保证原子性
          • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档