并发编程-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。

相关推荐
鬼火儿4 小时前
SpringBoot】Spring Boot 项目的打包配置
java·后端
cr7xin4 小时前
缓存三大问题及解决方案
redis·后端·缓存
间彧5 小时前
Kubernetes的Pod与Docker Compose中的服务在概念上有何异同?
后端
间彧5 小时前
从开发到生产,如何将Docker Compose项目平滑迁移到Kubernetes?
后端
间彧5 小时前
如何结合CI/CD流水线自动选择正确的Docker Compose配置?
后端
间彧5 小时前
在多环境(开发、测试、生产)下,如何管理不同的Docker Compose配置?
后端
间彧5 小时前
如何为Docker Compose中的服务配置健康检查,确保服务真正可用?
后端
间彧5 小时前
Docker Compose和Kubernetes在编排服务时有哪些核心区别?
后端
间彧5 小时前
如何在实际项目中集成Arthas Tunnel Server实现Kubernetes集群的远程诊断?
后端
brzhang6 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构