原子操作 订阅
"原子操作(atomic operation)是不需要synchronized",这是多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切 [1]  换到另一个线程)。 展开全文
"原子操作(atomic operation)是不需要synchronized",这是多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切 [1]  换到另一个线程)。
信息
特    性
不可分割的
外文名
atomic operation
含    义
是多个操作 不可被打乱或切割
中文名
原子操作
原子操作定义
如果这个操作所处的层(layer)的更高层不能发现其内部实现与结构,那么这个操作是一个原子(atomic)操作。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分。将整个操作视作一个整体是原子性的核心特征。
收起全文
精华内容
下载资源
问答
  • 原子操作

    万次阅读 2019-08-12 10:13:28
    1、原子操作 原子操作(atomic operation)指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。 现代操作系统中,一般都提供...

    1、原子操作

    原子操作(atomic operation)指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。

    现代操作系统中,一般都提供了原子操作来实现一些同步操作,所谓原子操作,也就是一个独立而不可分割的操作。在单核环境中,一般的意义下原子操作中线程不会被切换,线程切换要么在原子操作之前,要么在原子操作完成之后。更广泛的意义下原子操作是指一系列必须整体完成的操作步骤,如果任何一步操作没有完成,那么所有完成的步骤都必须回滚,这样就可以保证要么所有操作步骤都未完成,要么所有操作步骤都被完成。

    例如在单核系统里,单个的机器指令可以看成是原子操作(如果有编译器优化、乱序执行等情况除外);在多核系统中,单个的机器指令就不是原子操作,因为多核系统里是多指令流并行运行的,一个核在执行一个指令时,其他核同时执行的指令有可能操作同一块内存区域,从而出现数据竞争现象。多核系统中的原子操作通常使用内存栅障(memory barrier)来实现,即一个CPU核在执行原子操作时,其他CPU核必须停止对内存操作或者不对指定的内存进行操作,这样才能避免数据竞争问题。

    在C++11之前,C++标准中并没有对原子操作进行规定。vs和gcc编译器提供了原子操作的api。

    2、windows原子操作api

    Win32 API中常用的原子操作主要有三类,一种是加1减1操作,一种是比较交换操作,另外一种是赋值(写)操作。

    (1)原子加1减1操作

    LONG InterlockedIncrement( LONG volatile* Addend);

    LONG InterlockedDecrement( LONG volatile* Addend);

    (2)  比较并交换操作

    LONG InterlockedCompareExchange( LONG volatile*Destination, LONG Exchange, LONG Comperand );

    这个操作是先将Comperand的值和Destination指向变量的值进行比较,如果相等就将Exchange变量的值赋给Destination指向的变量。返回值为未修改前的Destination位置的初始值。

    (3)原子写操作

    LONG InterlockedExchange( LONG volatile* Target, LONG Value);

    InterlockedExchange的作用为将Value的值赋给Target指向的变量,返回Target指向变量未被赋值前的值。

    3、GCC编译器提供的原子操作API

    type __sync_fetch_and_add (type *ptr, type value);
    type __sync_fetch_and_sub (type *ptr, type value);
    type __sync_fetch_and_or (type *ptr, type value);
    type __sync_fetch_and_and (type *ptr, type value);
    type __sync_fetch_and_xor (type *ptr, type value);
    type __sync_fetch_and_nand (type *ptr, type value);
    type __sync_add_and_fetch (type *ptr, type value);
    type __sync_sub_and_fetch (type *ptr, type value);
    type __sync_or_and_fetch (type *ptr, type value);
    type __sync_and_and_fetch (type *ptr, type value);
    type __sync_xor_and_fetch (type *ptr, type value);
    type __sync_nand_and_fetch (type *ptr, type value);

    4、C++11提供的原子操作

    C++11中在<atomic>中定义了atomic模板类,atomic的模板参数类型可以为int、long、bool等等,C++中称为trivially copyable type。atomic_int、atomic_long为atomic模板实例化后的宏定义。atomic具体的原子操作函数可以参考http://www.cplusplus.com/reference/atomic/atomic/?kw=atomic

    5、原子操作的效率

    (1)不加锁也不使用原子变量的程序

    程序代码:

    输出:

    (2)加锁的程序

    程序代码:

    输出:

    3.使用C++11原子变量的程序

    程序代码和输出:

     (4)结论

    上面的第一个不加锁程序肯定是最不推荐使用的,因为它的执行结果都不正确。第二个程序是使用的常规锁来解决问题,结果正确,但是耗时较久。第三个程序使用的是C++11引入的原子数据类型,使用它程序结果正确,在运行速度上也比加锁的版本快很多。所以,在我们平常写程序的过程中,推荐使用C++11引入的原子变量。

    6、什么时候使用原子操作

    在多线程并发的条件下,所有不是原子性的操作需要保证原子性时,都需要进行原子操作处理。

    例:

    long count = 0;

    void func()

    {

      count++;

    }

    如果有n个线程同时执行这段代码,所有线程执行完后,count的值不一定等于n。因为count++不是一个原子操作,编译成汇编代码,如下所示:

    MOV   eax, [count] 

    INC  eax

    MOV [count], eax

    在cpu执行时 

    第一步,先将 count所在内存的值加载到寄存器;

    第二步,将寄存器的值自增1;

    第三步,将寄存器中的值写回内存。

    cout初始值为0。第一个线程执行加1后,执行到第二步,这时内存中cout值还是0,寄存器中cout值为1还没写入内存。这时cpu调度第二个线程,执行cout加1的操作是在内存中cout值为0的基础上的,是不对的,应该等第一个线程执行完后,内存中cout值被写入1后再进行加1。

    上述示例中,count的操作如果为count = count + 2,那么也需要原子操作,而如果为count=2或者count==2,则不需要原子操作,因为它们本身的操作就是具有原子性的。

    参考:

    http://www.cplusplus.com/reference/atomic/atomic/?kw=atomic

    https://blog.csdn.net/zhangqhn/article/details/80876177

    展开全文
  • 原子操作类AtomicInteger详解

    万次阅读 多人点赞 2018-06-08 15:33:17
    为什么需要AtomicInteger原子操作类? 对于Java中的运算操作,例如自增或自减,若没有进行额外的同步操作,在多线程环境下就是线程不安全的。num++解析为num=num+1,明显,这个操作不具备原子性,多线程并发共享这...

    为什么需要AtomicInteger原子操作类?

    对于Java中的运算操作,例如自增或自减,若没有进行额外的同步操作,在多线程环境下就是线程不安全的。num++解析为num=num+1,明显,这个操作不具备原子性,多线程并发共享这个变量时必然会出现问题。测试代码如下:

    public class AtomicIntegerTest {
    
        private static final int THREADS_CONUT = 20;
        public static int count = 0;
    
        public static void increase() {
            count++;
        }
    
        public static void main(String[] args) {
            Thread[] threads = new Thread[THREADS_CONUT];
            for (int i = 0; i < THREADS_CONUT; i++) {
                threads[i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 1000; i++) {
                            increase();
                        }
                    }
                });
                threads[i].start();
            }
    
            while (Thread.activeCount() > 1) {
                Thread.yield();
            }
            System.out.println(count);
        }
    }

    这里运行了20个线程,每个线程对count变量进行1000此自增操作,如果上面这段代码能够正常并发的话,最后的结果应该是20000才对,但实际结果却发现每次运行的结果都不相同,都是一个小于20000的数字。这是为什么呢?


    要是换成volatile修饰count变量呢?

    顺带说下volatile关键字很重要的两个特性:

    1、保证变量在线程间可见,对volatile变量所有的写操作都能立即反应到其他线程中,换句话说,volatile变量在各个线程中是一致的(得益于java内存模型—"先行发生原则");

    2、禁止指令的重排序优化;

    那么换成volatile修饰count变量后,会有什么效果呢? 试一试:

    public class AtomicIntegerTest {
    
        private static final int THREADS_CONUT = 20;
        public static volatile int count = 0;
    
        public static void increase() {
            count++;
        }
    
        public static void main(String[] args) {
            Thread[] threads = new Thread[THREADS_CONUT];
            for (int i = 0; i < THREADS_CONUT; i++) {
                threads[i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 1000; i++) {
                            increase();
                        }
                    }
                });
                threads[i].start();
            }
    
            while (Thread.activeCount() > 1) {
                Thread.yield();
            }
            System.out.println(count);
        }
    }
    

    结果似乎又失望了,测试结果和上面的一致,每次都是输出小于20000的数字。这又是为什么么? 上面的论据是正确的,也就是上面标红的内容,但是这个论据并不能得出"基于volatile变量的运算在并发下是安全的"这个结论,因为核心点在于java里的运算(比如自增)并不是原子性的。


    用了AtomicInteger类后会变成什么样子呢?

    把上面的代码改造成AtomicInteger原子类型,先看看效果

    import java.util.concurrent.atomic.AtomicInteger;
    
    public class AtomicIntegerTest {
    
        private static final int THREADS_CONUT = 20;
        public static AtomicInteger count = new AtomicInteger(0);
    
        public static void increase() {
            count.incrementAndGet();
        }
    
        public static void main(String[] args) {
            Thread[] threads = new Thread[THREADS_CONUT];
            for (int i = 0; i < THREADS_CONUT; i++) {
                threads[i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 1000; i++) {
                            increase();
                        }
                    }
                });
                threads[i].start();
            }
    
            while (Thread.activeCount() > 1) {
                Thread.yield();
            }
            System.out.println(count);
        }
    }

    结果每次都输出20000,程序输出了正确的结果,这都归功于AtomicInteger.incrementAndGet()方法的原子性。


    非阻塞同步

    同步:多线程并发访问共享数据时,保证共享数据再同一时刻只被一个或一些线程使用。

    我们知道,阻塞同步和非阻塞同步都是实现线程安全的两个保障手段,非阻塞同步对于阻塞同步而言主要解决了阻塞同步中线程阻塞和唤醒带来的性能问题,那什么叫做非阻塞同步呢?在并发环境下,某个线程对共享变量先进行操作,如果没有其他线程争用共享数据那操作就成功;如果存在数据的争用冲突,那就才去补偿措施,比如不断的重试机制,直到成功为止,因为这种乐观的并发策略不需要把线程挂起,也就把这种同步操作称为非阻塞同步(操作和冲突检测具备原子性)。在硬件指令集的发展驱动下,使得 "操作和冲突检测" 这种看起来需要多次操作的行为只需要一条处理器指令便可以完成,这些指令中就包括非常著名的CAS指令(Compare-And-Swap比较并交换)。《深入理解Java虚拟机第二版.周志明》第十三章中这样描述关于CAS机制:

    图取自《深入理解Java虚拟机第二版.周志明》13.2.2

    所以再返回来看AtomicInteger.incrementAndGet()方法,它的时间也比较简单

        /**
         * Atomically increments by one the current value.
         *
         * @return the updated value
         */
        public final int incrementAndGet() {
            for (;;) {
                int current = get();
                int next = current + 1;
                if (compareAndSet(current, next))
                    return next;
            }
        }
    

    incrementAndGet()方法在一个无限循环体内,不断尝试将一个比当前值大1的新值赋给自己,如果失败则说明在执行"获取-设置"操作的时已经被其它线程修改过了,于是便再次进入循环下一次操作,直到成功为止。这个便是AtomicInteger原子性的"诀窍"了,继续进源码看它的compareAndSet方法:

        /**
         * Atomically sets the value to the given updated value
         * if the current value {@code ==} the expected value.
         *
         * @param expect the expected value
         * @param update the new value
         * @return true if successful. False return indicates that
         * the actual value was not equal to the expected value.
         */
        public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }

    可以看到,compareAndSet()调用的就是Unsafe.compareAndSwapInt()方法,即Unsafe类的CAS操作。


    使用示例如下图,用于标识程序执行过程中是否发生了异常,使用quartz实现高级定制化定时任务(包含管理界面)实现中:


    2020-02-12补充如下内容:

    • 原子类相比于普通的锁,粒度更细、效率更高(除了高度竞争的情况下)
    • 如果对于上面的示例代码中使用了thread.yield()之类的方法不清晰的,可以直接看下面的代码压测:
    public class AtomicIntegerTest implements Runnable {
    
        static AtomicInteger atomicInteger = new AtomicInteger(0);
    
        static int commonInteger = 0;
    
        public void addAtomicInteger() {
            atomicInteger.getAndIncrement();
        }
    
        public void addCommonInteger() {
            commonInteger++;
        }
    
        @Override
        public void run() {
            //可以调大10000看效果更明显
            for (int i = 0; i < 10000; i++) {
                addAtomicInteger();
                addCommonInteger();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            AtomicIntegerTest atomicIntegerTest = new AtomicIntegerTest();
            Thread thread1 = new Thread(atomicIntegerTest);
            Thread thread2 = new Thread(atomicIntegerTest);
            thread1.start();
            thread2.start();
            //join()方法是为了让main主线程等待thread1、thread2两个子线程执行完毕
            thread1.join();
            thread2.join();
            System.out.println("AtomicInteger add result = " + atomicInteger.get());
            System.out.println("CommonInteger add result = " + commonInteger);
        }
    }
    • 原子类一览图参考如下:

    • 如何把普通变量升级为原子变量?主要是AtomicIntegerFieldUpdater<T>类,参考如下代码:
    /**
     * @description 将普通变量升级为原子变量
     **/
    public class AtomicIntegerFieldUpdaterTest implements Runnable {
    
        static Goods phone;
        static Goods computer;
    
        AtomicIntegerFieldUpdater<Goods> atomicIntegerFieldUpdater =
                AtomicIntegerFieldUpdater.newUpdater(Goods.class, "price");
    
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                phone.price++;
                atomicIntegerFieldUpdater.getAndIncrement(computer);
            }
        }
    
        static class Goods {
            //商品定价
            volatile int price;
        }
    
        public static void main(String[] args) throws InterruptedException {
            phone = new Goods();
            computer = new Goods();
            AtomicIntegerFieldUpdaterTest atomicIntegerFieldUpdaterTest = new AtomicIntegerFieldUpdaterTest();
            Thread thread1 = new Thread(atomicIntegerFieldUpdaterTest);
            Thread thread2 = new Thread(atomicIntegerFieldUpdaterTest);
            thread1.start();
            thread2.start();
            //join()方法是为了让main主线程等待thread1、thread2两个子线程执行完毕
            thread1.join();
            thread2.join();
            System.out.println("CommonInteger price = " + phone.price);
            System.out.println("AtomicInteger price = " + computer.price);
        }
    }
    • 在高并发情况下,LongAdder(累加器)比AtomicLong原子操作效率更高,LongAdder累加器是java8新加入的,参考以下压测代码:
    /**
     * @description 压测AtomicLong的原子操作性能
     **/
    public class AtomicLongTest implements Runnable {
    
        private static AtomicLong atomicLong = new AtomicLong(0);
    
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                atomicLong.incrementAndGet();
            }
        }
    
        public static void main(String[] args) {
            ExecutorService es = Executors.newFixedThreadPool(30);
            long start = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                es.submit(new AtomicLongTest());
            }
            es.shutdown();
            //保证任务全部执行完
            while (!es.isTerminated()) { }
            long end = System.currentTimeMillis();
            System.out.println("AtomicLong add 耗时=" + (end - start));
            System.out.println("AtomicLong add result=" + atomicLong.get());
        }
    }
    /**
     * @description 压测LongAdder的原子操作性能
     **/
    public class LongAdderTest implements Runnable {
    
        private static LongAdder longAdder = new LongAdder();
    
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                longAdder.increment();
            }
        }
    
        public static void main(String[] args) {
            ExecutorService es = Executors.newFixedThreadPool(30);
            long start = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                es.submit(new LongAdderTest());
            }
            es.shutdown();
            //保证任务全部执行完
            while (!es.isTerminated()) {
            }
            long end = System.currentTimeMillis();
            System.out.println("LongAdder add 耗时=" + (end - start));
            System.out.println("LongAdder add result=" + longAdder.sum());
        }
    }

    在高度并发竞争情形下,AtomicLong每次进行add都需要flush和refresh(这一块涉及到java内存模型中的工作内存和主内存的,所有变量操作只能在工作内存中进行,然后写回主内存,其它线程再次读取新值),每次add()都需要同步,在高并发时会有比较多冲突,比较耗时导致效率低;而LongAdder中每个线程会维护自己的一个计数器,在最后执行LongAdder.sum()方法时候才需要同步,把所有计数器全部加起来,不需要flush和refresh操作。


    books 参考资料:书籍《深入理解Java虚拟机 第二版》、慕课网实战课程《玩转Java并发工具,精通JUC,成为并发多面手

    books 引申阅读: 使用quartz实现高级定制化定时任务(包含管理界面)

    books 推荐阅读:elastic search搜索引擎实战demo:https://github.com/simonsfan/springboot-quartz-demo,分支:feature_es

    展开全文
  • //Interlocked为原子操作相关类,a值为最终值,b可能因为同时操作返回不同的值 public static class AtomicSaple { public static int a = 0; public static void Sample() { var b = 0; b = Interlocked....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 43,815
精华内容 17,526
关键字:

原子操作