美文网首页
原子操作

原子操作

作者: 一颗北上广的心 | 来源:发表于2017-03-27 14:25 被阅读0次

    总线锁(CPU总线):

    CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性.

    缓存锁:

    频繁使用的内存会缓存在处理器的L1,L2和L3高速缓存里,那么原子操作就可以直接在处理器内部缓存中进行,并不需要声明总线锁。

    所谓“缓存锁定”就是如果缓存在处理器缓存行中内存区域在LOCK操作期间被锁定,当它执行锁操作回写内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制(当某块CPU对缓存中的数据进行操作了之后,就通知其他CPU放弃储存在它们内部的缓存,或者从主内存中重新读取)来保证操作的原子性.

    JAVA原子操作:

    在java中可以通过锁和循环CAS(compare and swap)的方式来实现原子操作。

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class T {
    
        public static void main(String[] args) {
    
            List<Thread> threads = new ArrayList<>();
            for (int i = 0; i < 1000; i++) {
                threads.add(new Thread(new MyThread()));
            }
    
            for (Thread t : threads) {
                t.start();
            }
    
            for (Thread t : threads) {
                try {
                    t.join();
                } catch (InterruptedException e) {
                }
            }
    
            System.out.println("counter_i:" + MyThread.counter_i);
            System.out.println("counter_volatile:" + MyThread.counter_volatile);
            System.out.println("counter_automic:" + MyThread.counter_cas.get());
            System.out.println("counter_locker:" + MyThread.counter_locker);
        }
    
    }
    
    class MyThread implements Runnable {
    
        static int counter_i;
        static volatile int counter_volatile;
        static AtomicInteger counter_cas;
        static int counter_locker;
    
        static {
            counter_cas = new AtomicInteger(0);
            counter_i = 0;
            counter_volatile = 0;
            counter_locker = 0;
        }
    
        @Override
        public void run() {
            // 未保证原子型操作
            counter_i++;
            // 未保证原子型操作
            counter_volatile++;
    
            int i;
            do {
                // 保证了原子型操作
                i = counter_cas.get();
            } while (!counter_cas.compareAndSet(i, ++i));
    
            // 保证了原子型操作
            synchronized (MyThread.class) {
                counter_locker++;
            }
    
        }
    
    }
    
    

    分析:

    线程中i++可拆解成如下操作:
    a. 从主存复制变量到线程本地内存
    b. 读取线程本地内存i
    c. 线程本地内存i+1
    d. 写回线程本地内存
    e. 用线程本地内存数据刷新主存相关内容

    直接i++有很多种case会出现问题

    分析略

    volatile i ++

    虽然告诉JVM当前变量在寄存器/高速缓存(工作内存)中的值是不确定的,需要从主存中读取), 使修改对其他线程可见,但是仍然没有作到i++的原子操作. volatile无法保证复合操作的原子性。

    AtomicInteger 循环使用CAS

    基于处理器CMPXCHG,保证了原子性. Java的AtomicInteger使用上面提到的系统级别的总线锁和缓存锁来保证原子操作。
    AtomicInteger最终调用

    //object:操作的对象
    //address:操作对象的属性地址
    //expected:预期值
    //newValue:新值
    compareAndSwapInt(Object object, long address, int expected, int newValue) 
    

    CAS有几个缺点:

    • ABA:
      例如有链表 A->B
      线程1 compareAndSet(A,B), compare A 之前, 线程2 将链表结构变为A->C,线程1compare A OK(A地址未变化),并Swap A和 B,链表结构变为B,和预期的B->A不同.
      解决办法:使用版本戳,java 中的AtomicStampedReference实现了该功能。
    • 循环时间长
    • 只能操作一个变量
      解决办法:将多个变量封装在一个对象中,使用AtomicReference<V>。
    synchronized 对i++操作整体加锁,保证了原子性.

    synchronized关键字强制实施一个互斥锁,使得被保护的代码块在同一时间只能有一个线程进入并执行。当然synchronized还有另外一个 方面的作用:在线程进入synchronized块之前,会把工作存内存中的所有内容映射到主内存上,然后把工作内存清空再从主存储器上拷贝最新的值。而 在线程退出synchronized块时,同样会把工作内存中的值映射到主内存,但此时并不会清空工作内存。这样一来就可以强制其按照上面的顺序运行,以 保证线程在执行完代码块后,工作内存中的值和主内存中的值是一致的,保证了数据的一致性!

    参考资料:

    原子操作的实现原理
    关于单CPU,多CPU上的原子操作
    聊聊并发(五)——原子操作的实现原理
    CAS原理分析
    java 里面保留字volatile及其与synchronized的区别

    相关文章

      网友评论

          本文标题:原子操作

          本文链接:https://www.haomeiwen.com/subject/lsrrottx.html