精华内容
下载资源
问答
  • 原子操作类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

    展开全文
  • 原子操作

    万次阅读 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

    展开全文
  • 在并发编程中有三个非常重要的特性:原子性、有序性,、可见性,学妹发现你对它们不是很了解,她很着急,因为理解这三个特性对于能够正确地开发高并发程序有很大的帮助,接下来的面试中也极有可能被问到,小学妹就忍...

    在并发编程中有三个非常重要的特性:原子性、有序性,、可见性,学妹发现你对它们不是很了解,她很着急,因为理解这三个特性对于能够正确地开发高并发程序有很大的帮助,接下来的面试中也极有可能被问到,小学妹就忍不住开始跟你逐一介绍起来。

    Java内存模型

    在讲三大特性之前先简单介绍一下Java内存模型(Java Memory Model,简称JMM),了解了Java内存模型以后,可以更好地理解三大特性。

    Java内存模型是一种抽象的概念,并不是真实存在的,它描述的是一组规范或者规定。JVM运行程序的实体是线程,每一个线程都有自己私有的工作内存。Java内存模型中规定了所有变量都存储在主内存中,主内存是一块共享内存区域,所有线程都可以访问。但是线程对变量的读取赋值等操作必须在自己的工作内存中进行,在操作之前先把变量从主内存中复制到自己的工作内存中,然后对变量进行操作,操作完成后再把变量写回主内存。线程不能直接操作主内存中的变量,线程的工作内存中存放的是主内存中变量的副本。

    原子性(Atomicity)

    什么是原子性

    原子性是指:在一次或者多次操作时,要么所有操作都被执行,要么所有操作都不执行。

    一般说到原子性都会以银行转账作为例子,比如张三向李四转账100块钱,这包含了两个原子操作:在张三的账户上减少100块钱;在李四的账户上增加100块钱。这两个操作必须保证原子性的要求,要么都执行成功,要么都执行失败。不能出现张三的账户减少100块钱而李四的账户没增加100块钱,也不能出现张三的账户没减少100块钱而李四的账户却增加100块钱。

    原子性示例

    示例一
    i = 1;
    

    根据上面介绍的Java内存模型,线程先把i=1写入工作内存中,然后再把它写入主内存,就此赋值语句可以说是具有原子性。

    示例二
    i = j;
    

    这个赋值操作实际上包含两个步骤:线程从主内存中读取j的值,然后把它存入当前线程的工作内存中;线程把工作内存中的i改为j的值,然后把i的值写入主内存中。虽然这两个步骤都是原子性的操作,但是合在一起就不是原子性的操作。

    示例三
    i++;
    

    这个自增操作实际上包含三个步骤:线程从主内存中读取i的值,然后把它存入当前线程的工作内存中;线程把工作内存中的i执行加1操作;线程再把i的值写入主内存中。和上一个示例一样,虽然这三个步骤都是原子性的操作,但是合在一起就不是原子性的操作。

    从上面三个示例中,我们可以发现:简单的读取和赋值操作是原子性的,但把一个变量赋值给另一个变量就不是原子性的了;多个原子性的操作放在一起也不是原子性的。

    如何保证原子性

    在Java内存模型中,只保证了基本读取和赋值的原子性操作。如果想保证多个操作的原子性,需要使用synchronized关键字或者Lock相关的工具类。如果想要使int、long等类型的自增操作具有原子性,可以用java.util.concurrent.atomic包下的工具类,如:AtomicIntegerAtomicLong等。另外需要注意的是,volatile关键字不具有保证原子性的语义。

    可见性(Visibility)

    什么是可见性

    可见性是指:当一个线程对共享变量进行修改后,另外一个线程可以立即看到该变量修改后的最新值。

    可见性示例

    package onemore.study;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class VisibilityTest {
        public static int count = 0;
    
        public static void main(String[] args) {
            final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    
            //读取count值的线程
            new Thread(() -> {
                System.out.println("开始读取count...");
                int i = count;//存放count的更新前的值
                while (count < 3) {
                    if (count != i) {//当count的值发生改变时,打印count被更新
                        System.out.println(sdf.format(new Date()) + " count被更新为" + count);
                        i = count;//存放count的更新前的值
                    }
                }
            }).start();
    
            //更新count值的线程
            new Thread(() -> {
                for (int i = 1; i <= 3; i++) {
                    //每隔1秒为count赋值一次新的值
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(sdf.format(new Date()) + " 赋值count为" + i);
                    count = i;
    
                }
            }).start();
        }
    }
    

    在运行代码之前,先想一下运行的输出是什么样子的?在更新count值的线程中,每一次更新count以后,在读取count值的线程中都会有一次输出嘛?让我们来看一下运行输出是什么:

    开始读取count...
    17:21:54.796 赋值count为1
    17:21:55.798 赋值count为2
    17:21:56.799 赋值count为3
    

    从运行的输出看出,读取count值的线程一直没有读取到count的最新值,这是为什么呢?因为在读取count值的线程中,第一次读取count值时,从主内存中读取count的值后写入到自己的工作内存中,再从工作内存中读取,之后的读取的count值都是从自己的工作内存中读取,并没有发现更新count值的线程对count值的修改。

    如何保证可见性

    在Java中可以用以下3种方式保证可见性。

    使用volatile关键字

    当一个变量被volatile关键字修饰时,其他线程对该变量进行了修改后,会导致当前线程在工作内存中的变量副本失效,必须从主内存中再次获取,当前线程修改工作内存中的变量后,同时也会立刻将其修改刷新到主内存中。

    使用synchronized关键字

    synchronized关键字能够保证同一时刻只有一个线程获得锁,然后执行同步方法或者代码块,并且确保在锁释放之前,会把变量的修改刷新到主内存中。

    使用Lock相关的工具类

    Lock相关的工具类的lock方法能够保证同一时刻只有一个线程获得锁,然后执行同步代码块,并且确保执行Lock相关的工具类的unlock方法在之前,会把变量的修改刷新到主内存中。

    有序性(Ordering)

    什么是有序性

    有序性指的是:程序执行的顺序按照代码的先后顺序执行。

    在Java中,为了提高程序的运行效率,可能在编译期和运行期会对代码指令进行一定的优化,不会百分之百的保证代码的执行顺序严格按照编写代码中的顺序执行,但也不是随意进行重排序,它会保证程序的最终运算结果是编码时所期望的。这种情况被称之为指令重排(Instruction Reordering)。

    有序性示例

    package onemore.study;
    
    public class Singleton {
        private Singleton (){}
    
        private static boolean isInit = false;
        private static Singleton instance;
    
        public static Singleton getInstance() {
            if (!isInit) {//判断是否初始化过
                instance = new Singleton();//初始化
                isInit = true;//初始化标识赋值为true
            }
            return instance;
        }
    }
    

    这是一个有问题的单例模式示例,假如在编译期或运行期时指令重排,把isInit = true;重新排序到instance = new Singleton();的前面。在单线程运行时,程序重排后的执行结果和代码顺序执行的结果是完全一样的,但是多个线程一起执行时就极有可能出现问题。比如,一个线程先判断isInit为false进行初始化,本应在初始化后再把isInit赋值为true,但是因为指令重排没后初始化就把isInit赋值为true,恰好此时另外一个线程在判断是否初始化过,isInit为true就执行返回了instance,这是一个没有初始化的instance,肯定造成不可预知的错误。

    如何保证有序性

    这里就要提到Java内存模型的一个叫做先行发生(Happens-Before)的原则了。如果两个操作的执行顺序无法从Happens-Before原则推到出来,那么可以对它们进行随意的重排序处理了。Happens-Before原则有哪些呢?

    • 程序次序原则:一段代码在单线程中执行的结果是有序的。
    • 锁定原则:一个锁处于被锁定状态,那么必须先执行unlock操作后面才能进行lock操作。
    • volatile变量原则:同时对volatile变量进行读写操作,写操作一定先于读操作。
    • 线程启动原则:Thread对象的start方法先于此线程的每一个动作。
    • 线程终结原则:线程中的所有操作都先于对此线程的终止检测。
    • 线程中断原则:对线程interrupt方法的调用先于被中断线程的代码检测到中断事件的发生。
    • 对象终结原则:一个对象的初始化完成先于它的finalize方法的开始。
    • 传递原则:操作A先于操作B,操作B先于操作C,那么操作A一定先于操作C。

    除了Happens-Before原则提供的天然有序性,我们还可以用以下几种方式保证有序性:

    • 使用volatile关键字保证有序性。
    • 使用synchronized关键字保证有序性。
    • 使用Lock相关的工具类保证有序性。

    总结

    • 原子性:在一次或者多次操作时,要么所有操作都被执行,要么所有操作都不执行。
    • 可见性:当一个线程对共享变量进行修改后,另外一个线程可以立即看到该变量修改后的最新值。
    • 有序性:程序执行的顺序按照代码的先后顺序执行。

    synchronized关键字和Lock相关的工具类可以保证原子性、可见性和有序性,volatile关键字可以保证可见性和有序性,不能保证原子性。

    文章持续更新,微信搜索「 万猫学社 」第一时间阅读。
    关注后回复「 电子书 」,免费获取12本Java必读技术书籍。

    展开全文
  • 原子变量和原子操作

    2019-10-30 15:44:47
    1、什么是原子变量和原子操作 原子操作是指**不会**被线程调度机制**打断**的操作;原子操作一旦开始,就一直运行到结束,中间**不会切换**到任何别的进程。 原子变量是原子操作的基本单位。 2、原子变量和原子操作...

    1、什么是原子变量和原子操作
    原子操作是指不会被线程调度机制打断的操作;原子操作一旦开始,就一直运行到结束,中间不会切换到任何别的进程。
    原子变量是原子操作的基本单位。

    2、原子变量和原子操作的功能是什么
    在多进程(线程)访问共享资源时,能够确保所有其他的进程(线程)都不在同一时间内访问相同的资源。
    例如,atomic_bool,atomic_int等等,如果我们在多个线程中对这些类型的共享资源进行操作,编译器将保证这些操作都是原子性的,也就是说,确保任意时刻只有一个线程对这个资源进行访问,编译器将保证,多个线程访问这个共享资源的正确性。从而避免了锁的使用,提高了效率。
    定义一个变量,将对一个变量值的读取、修改、回写变成一个不可打断的操作,这个变量就是原子变量。

    3、原子变量和原子操作的应用
    常用原子操作函数举例:

    		atomic_t v = ATOMIC_INIT(0); //定义原子变量v并初始化为0
    		atomic_**read**(atomic_t *v); //返回原子变量的值
    		void atomic_**inc**(atomic_t *v); //原子变量增加1
    		void atomic_**dec**(atomic_t *v); //原子变量减少1
    		int atomic_**dec_and_test**(atomic_t *v); //自减操作后测试其是否为0,为0则返回true,否则返回false。
    

    常见的原子操作举例:

    		#define atomic_**read**(v) ((v)->counter) //读取v指向的原子变量的值
    		#define atomic_**set**(v,i) (((v)->counter) = (i)) //设置v指向的原子变量的值为i。
    		static *__inline__* void atomic_**sub**(int i,atomic_t *v)  //从v指向的原子变量减去i。
    		static __inline__ void atomic_**inc**(atomic_t *v) //递增v指向的原子变量。
    

    C++11下的用法:

    		#include<atomic>
    		atomic_int count(0);//声明并初始化
    

    4、方法

    getAndSet - >原子设置为给定值并返回上一个值。

    compareAndSet - >”如果当前值==期望值,则以原子方式将值设置为给定的更新值。

    getandincrement :得到当前值,并自增一。https://blog.csdn.net/camelcanoe/article/details/85099337

    incrementAndGet:

    通过源码分析可知,incrementAndGet()和 getAndIncrement ()都调用了 Unsafe 类中的 getAndAddInt() 方法,区别是:

    ① 前者,先+1,再返回

    ② 后者,先返回,再 +1

    展开全文
  • C++11 原子类型与原子操作

    千次阅读 2019-01-02 00:21:41
    1.认识原子操作 原子操作就是在多线程程序中“最小的且不可并行化的”操作,意味着多个线程访问同一个资源时,有且仅有一个线程能对资源进行操作。通常情况下原子操作可以通过互斥的访问方式来保证,例如Linux下的...
  • 原子

    千次阅读 2019-02-19 20:11:52
    什么是原子类  以前认为原子是不可分割的最小单位。故原子类可以认为其操作时不可分割的。  为什么要有原子类  对多线程访问同一变量,我们需要加锁,而锁是比较消耗性能的,jdk1.5之后,新增的原子操作类提供...
  • 原子操作和非原子操作

    千次阅读 2019-06-26 21:46:32
    今天在学习多线程时突然想到一个问题,CPU的CAS操作保证了原子性,但是现在的CPU都是多核心可以并行运算的多核CPU了,那CPU怎么实现并行运算时而又能保证对内存操作的原子性呢? 于是查阅了一些资料,有了以下了解...
  • 原子性与原子操作

    千次阅读 2017-10-12 15:27:57
    原子性:如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行。 原子性达到的目标:就是能使一个程序被完整的执行。 原子操作:不可被中断的一个或一系列的操作。 CAS有3个操作数,内存值V,...
  • 原子广播

    千次阅读 2019-04-10 16:49:51
    原子广播 学区块链这么久,对原子广播这个早有听说,但一直不明白其具体内涵,查阅wiki,原子广播是一个分布式原语,其保证各节点收到并相同次序的消息并处理或者没有副作用的中止操作。 In fault-tolerant ...
  • 诡异的并发之原子

    千次阅读 多人点赞 2020-03-03 09:19:24
    上一节我和大家一起打到了并发中的恶霸可见性,这一节我们继续讨伐三恶之一的原子性。 序、原子性的阐述 一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性。 我理解是一个操作不可再分,即为原子性。而...
  • 原子和非原子属性

    千次阅读 2015-10-11 00:28:53
    一、原子和非原子属性 1. OC在定义属性时有nonatomic和atomic两种选择: atomic:原子属性,为setter方法加锁(默认就是atomic); nonatomic:非原子属性,不会为setter方法加锁。 2. ...
  • 原子操作类总结

    千次阅读 2019-10-14 21:10:53
    文章目录原子操作类简介预备知识-CAS操作原子更新基本类型原子更新数组类型原子更新引用类型原子更新字段类型 原子操作类简介 在并发编程中很容易出现并发安全的问题,有一个很简单的例子就是多线程更新变量i=1,比如...
  • 什么是java的原子性? 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。 一个很经典的例子就是银行账户转账问题: 比如从账户A向账户B转1000元,那么必然包括2个...
  • 原子云初体验——ESP8266连接原子

    千次阅读 2019-04-13 22:56:06
    esp8266物联网(一)—— 原子云初体验具体实现: 最近在玩 esp8266 ,打算做一个可以用PC端和手机端远程操控的物联网设备,跟同学借了一块正点原子的esp8266,在正点原子论坛查了一下,竟然有免费的云服务器可以...
  • 在 Java并发12:并发三特性-原子性、可见性和有序性概述及问题示例中,对并发中的三个特性(原子性、可见性和有序性)进行了初步学习。 本章主要就Java中保障原子性的技术进行更加全面的学习。 1.整体回顾 原子性...
  • 在网上已经有很多有关介绍原子操作的内容,通常都是注重于原子读-修改-写(RMW)操作。然而,这些并不是原子操作的全部,还有同样重要的原子加载和原子存储。在这篇文章中,我将要在处理器级别和C/C++语言级别两个...
  • 原子操作Atomic类

    万次阅读 2020-05-06 14:52:45
    Synchronized存在的弊端,原子操作Atomic类CAS机制讲解,以及ABA问题的提出和解决方案
  • 原子属性

    千次阅读 2016-06-30 21:56:22
    原子属性(线程安全),是针对多线程设计的,是默认属性多个线程在写入原子属性时(调用 setter 方法),能够保证同一时间只有一个线程执行写入操作原子属性是一种单(线程)写多(线程)读的多线程技术原子属性的效率...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 90,038
精华内容 36,015
关键字:

原子