并发编程-Atomic原子操作详解

引言

欢迎大家来到并发编程第四期-Atomic原子类详解,在之前我们学习了JMM,ConcurrentHashMap,线程 池,现在我们来学习一个并发工具类-Atomic。学完了这篇文章,对你并发编程的工具使用会有很大的提升。

什么是Atomic原子类

大家都知道并发编程中会有线程安全问题,至于线程安全我例子就不说了。解决方法最常见的就是加Synchronized锁,在这之前我们也介绍过,这是一个重量级锁,也是一个悲观锁,由于CPU不停的自旋,就会导致性能的损耗,那么我们就需要更好的解决方案。这时Atomic横空出世。

在JUC下有一个Atomic原子类,操作简单,性能高效,而且能保证线程安全的同时做到效率高。实际上,效率高的原因是Atomic锁机制是乐观锁,底层使用了CAS。

java.util.concurrent.atomic下的一组原子操作类

基本类型:AtomicInteger、AtomicLong、AtomicBoolean;

引用类型:AtomicReference、AtomicStampedRerence、AtomicMarkableReference;

数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

对象属性原子修改器:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、 AtomicReferenceFieldUpdater

原子类型累加器(jdk1.8增加的类):DoubleAccumulator、DoubleAdder、 LongAccumulator、LongAdder、Striped64

原子更新基本类型

以AtomicInteger常用方法举例

Java 复制代码
// 以原子的方式将实例中的原值加1,返回的是自增前的旧值;
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

// getAndSet(int newValue):将实例中的值更新为新值,并返回旧值;
public final boolean getAndSet(boolean newValue) {
    boolean prev;
    do {
        prev = get();
    } while (!compareAndSet(prev, newValue));
    return prev;
}

// incrementAndGet() :以原子的方式将实例中的原值进行加1操作,并返回最终相加后的结果;
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

// addAndGet(int delta) :以原子方式将输入的数值与实例中原本的值相加,并返回最后的结果;
public final int addAndGet(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}

测试代码

Java 复制代码
public class AtomicIntegerTest {
    static AtomicInteger sum = new AtomicInteger(0);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    // 原子自增 CAS
                    sum.incrementAndGet();
                    //TODO
                }
            });
            thread.start();
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(sum.get());
    }
}

测试结果

大家都知道如果直接100000个线程去加同一个变量,那么肯定会线程不安全,最后得到的结果肯定不是100000,但是通过这个线程的工具类就保证了线程安全。这是因为底层是通过CAS原子指令操作的,至于什么是CAS,简单来说,它不是通过加锁来控制变量修改的,他是通过前后变量的比较,再通过偏移量来确定是否是上一个变量,如果是,那么就可以修改,如果不是,就修改失败,证明已经有其他线程进行修改了,而这一个线程已经晚了,所有保证了线程安全。

缺点 但是,CAS是通过CPU的不断自旋来确定是否是这个变量,所以这就很消耗CPU性能,当很多请求时,就会造成服务器的资源耗尽

原子类更新数组内容

java 复制代码
//addAndGet(int i, int delta):以原子更新的方式将数组中索引为i的元素与输入值相加;
public final int addAndGet(int i, int delta) {
    return getAndAdd(i, delta) + delta;
}

//getAndIncrement(int i):以原子更新的方式将数组中索引为i的元素自增加1;
public final int getAndIncrement(int i) {
    return getAndAdd(i, 1);
}

//compareAndSet(int i, int expect, int update):将数组中索引为i的位置的元素进行更新
public final boolean compareAndSet(int i, int expect, int update) {
    return compareAndSetRaw(checkedByteOffset(i), expect, update);
}

测试原子类更新数组

java 复制代码
public class AtomicIntegerArrayTest {
    static int[] value = new int[]{ 1, 2, 3, 4, 5 };
    static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(value);

    public static void main(String[] args) throws InterruptedException {
        //设置索引0的元素为100
        atomicIntegerArray.set(0, 100);
        System.out.println(atomicIntegerArray.get(0));
        //以原子更新的方式将数组中索引为1的元素与输入值相加
        atomicIntegerArray.getAndAdd(1, 5);
        System.out.println(atomicIntegerArray);
    }
}

测试结果:

原子类更新引用类型

java 复制代码
public class AtomicReferenceTest {
    public static void main(String[] args) {
        User user1 = new User("张三", 23);
        User user2 = new User("李四", 25);
        User user3 = new User("王五", 20);

        //初始化为 user1
        AtomicReference<User> atomicReference = new AtomicReference<>();
        atomicReference.set(user1);

        //把 user2 赋给 atomicReference
        atomicReference.compareAndSet(user1, user2);
        System.out.println(atomicReference.get());

        //把 user3 赋给 atomicReference
        atomicReference.compareAndSet(user1, user3);
        System.out.println(atomicReference.get());
    }
}

@Data
@AllArgsConstructor
class User {
    private String name;
    private Integer age;
}

这段代码把引用类型user1赋值给atomicReference,其中类AtomicReference是一个泛型类,这说明这个类可以传入其他类,也是atomic原子类为了更新所有引用类型做的设计。之后把user2,user3线程安全的赋值给atomicReference。

相关推荐
可观测性用观测云1 小时前
ArgoCD 可观测性最佳实践
后端
于过2 小时前
基于Mybatis的SQL模版解析
后端·mybatis
lamdaxu2 小时前
Java基础--异常机制详解
后端
宦如云2 小时前
Bash语言的哈希表
开发语言·后端·golang
失业写写八股文2 小时前
本地事务 vs 分布式事务:核心区别与解释
分布式·后端
uhakadotcom3 小时前
快速构建交互式数据应用:Streamlit入门指南
后端·面试·github
无名之逆3 小时前
hyperlane:Rust HTTP 服务器开发的不二之选
服务器·开发语言·前端·后端·安全·http·rust
机构师3 小时前
<iced><rust><GUI>基于rust的GUI库iced的学习(02):svg图片转png
后端·rust
老赵骑摩托3 小时前
Go语言nil原理深度解析:底层实现与比较规则
开发语言·后端·golang