• Atomic原子类详解


    为什么需要Atomic原子操作类?
    在并发环境中,代码如果操作相同的数据,就会产生资源竞争,导致结果远小于预期值
    例如在A线程B线程中同时获取到变量数据为1,同时执行变量+1操作,结果可能也是1,存在脏读幻读问题,因为在同一个进程中,资源是共享的,因此需要进行原子操作

    volatile关键字

    作用: 禁止CPU缓存,直接从主内存获取数据,更新数据时通知到其他线程,保证线程运行时候数据的可见性,防止指令重排
    在并发环境中,如果多个线程进行资源竞争,依旧无法解决脏读幻读问题,有序问题

    CAS原理

    CAS (compareAndSwap),中文叫比较交换,是一种无锁原子算法,映射到操作系统就是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,其实现方式是基于硬件平台的汇编指令,在intel的CPU中,使用的是cmpxchg指令,就是说CAS是靠硬件实现的,从而在硬件层面提升效率。
    执行过程是这样:它包含 3 个参数 CAS(V,E,N),V表示要更新变量的值,E表示预期值,N表示新值。仅当 V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程完成更新,则当前线程则什么都不做,最后CAS 返回当前V的真实值。
    当多个线程同时使用CAS 操作一个变量时,最多只有一个会胜出,并成功更新,其余均会失败。失败的线程不会挂起,仅是被告知失败,并且允许再次尝试(自旋),当然也允许实现的线程放弃操作。基于这样的原理,CAS 操作即使没有锁,也可以避免其他线程对当前线程的干扰。

    AtomicObject实现

    Unsafe是CAS的核心类,Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。不过尽管如此,JVM还是开了一个后门:Unsafe,它提供了硬件级别的原子操作
    package org.example;

    import sun.misc.Unsafe;
    
    import java.lang.reflect.Field;
    import java.util.function.UnaryOperator;
    
    public class AtomicObject<V> implements java.io.Serializable {
        private static final long serialVersionUID = 4654671469794556979L;
        private static final Unsafe unsafe;
    
        private static final long valueOffset;
    
        static {
            try {
                //获取属性
                Field field = Unsafe.class.getDeclaredField("theUnsafe");
                field.setAccessible(true);
                unsafe = (Unsafe) field.get(null);
                valueOffset = unsafe.objectFieldOffset
                        (AtomicObject.class.getDeclaredField("value"));
            } catch (Exception ex) {
                throw new Error(ex);
            }
        }
    
        private volatile V value;
    
    
        public AtomicObject(V v){
            this.value = v;
        }
    
    
        public final V get() {
            return value;
        }
    
          /**
         * 比较当前对象的引用值是否等于预期值,如果相等,则将对象的引用值设置为新值,并返回true,
         * 否则不做任何操作,并返回 false
         * 将对象进行偏移量,和预期值进行比较
         * @param expect 预期值
         * @param update 更新值
         * @return boolean
         */
        public final boolean compareAndSet(V expect, V update) {
            return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
        }
        
        
        /**
         * 如果当前值不是预期值,则自旋,直到当前值是预期值,并返回当前值
         * @param updateFunction 更新的方法
         * @return v 当前值
         */
        public final V getAndUpdate(UnaryOperator<V> updateFunction) {
            V prev, next;
            do {
                prev = get();
                next = updateFunction.apply(prev);
            } while (!compareAndSet(prev, next));
            return prev;
        }
    
    
        public final V getAndUpdate(V next) {
            V prev;
            do {
                prev = get();
            } while (!compareAndSet(prev, next));
            return prev;
        }
    
    
        public long getValueOffset(){
            return valueOffset;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    其中,expect参数是预期的对象引用值,update参数是要更新的新对象引用值。
    compareAndSwapObject方法的实现使用了CPU的原子操作指令,可以保证在多线程环境下的线程安全性。它通常用于实现一些需要线程安全的对象引用更新操作,例如单例模式的实现、缓存的更新等。
    需要注意的是,compareAndSwapObject方法只能保证对象引用的线程安全性,如果对象本身不是线程安全的,则需要使用其他的同步机制来保证对象的线程安全性

    代码体现:
    首先检查当前对象是否持有预期值,如果持有则,更新x为新值

    简单的说,CAS 需要你额外给出一个期望值,也就是你认为这个变量现在应该是什么样子的。如果变量不是你想象的那样,说明它已经被别人修改过了。你就需要重新读取,再次尝试修改就好了。

    // Unsafe.h
    virtual jboolean compareAndSwapObject(::java::lang::Object *, jlong, ::java::lang::Object *, ::java::lang::Object *);
    
    // natUnsafe.cc
    static inline bool
    compareAndSwap (volatile jobject *addr, jobject old, jobject new_val)
    {
        jboolean result = false;
        spinlock lock;
      
              // 如果字段的地址与期望的地址相等则将字段的地址更新
            if ((result = (*addr == old)))
                *addr = new_val;
            return result;
    }
    
    // natUnsafe.cc
    jboolean sun::misc::Unsafe::compareAndSwapObject (jobject obj, jlong offset,
                         jobject expect, jobject update)
    {
            // 获取字段地址并转换为字符串
        jobject *addr = (jobject*)((char *) obj + offset);
            // 调用 compareAndSwap 方法进行比较
        return compareAndSwap (addr, expect, update);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    CAS缺点
    CAS虽然高效地解决了原子操作,但是还是存在一些缺陷的,主要表现在三个方面:
    1.自旋时间太长
    2.只能保证一个共享变量原子操作
    3.ABA问题,不能保证线程的顺序问题

    壁纸来自《仙剑问情女神》画师:炼丹 高清无水印原图4K壁纸

  • 相关阅读:
    Docker 命令
    Catalan 数 和 Stirling 数
    docker centos7容器中文乱码问题解决
    React中的生命周期函数
    C语言——自定义类型之枚举
    Kratos战神微服务框架(二)
    JAVA毕业设计的工资管理系统计算机源码+lw文档+系统+调试部署+数据库
    数据库1(新手易懂,超详细)
    Linux系统编程(三):进程
    Ansible role
  • 原文地址:https://blog.csdn.net/weixin_45904404/article/details/133711110