zoukankan      html  css  js  c++  java
  • Java并发编程系列-(3) 原子操作与CAS

    目前已经更新完《Java并发编程》,《Docker教程》和《JVM性能优化》,欢迎关注【后端精进之路】,轻松阅读全部文章。

    Java并发编程:

    Docker教程:

    JVM性能优化:

    3. 原子操作与CAS

    3.1 原子操作

    所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何context switch,也就是切换到另一个线程。

    为了实现原子操作,Java中可以通过synchronized关键字将函数或者代码块包围,以实现操作的原子性。但是synchronized关键字有一些很显著的问题:

    1、synchronized是基于阻塞锁的机制,如果被阻塞的线程优先级很高,可能很长时间其他线程都没有机会运行;
    2、拿到锁的线程一直不释放锁,可能导致其他线程一直等待;
    3、线程数量很多时,可能带来大量的竞争,消耗cpu,同时带来死锁或者其他安全。

    像synchronized这种独占锁属于悲观锁,它是在假设一定会发生冲突的,那么加锁恰好有用,除此之外,还有乐观锁,乐观锁的含义就是假设没有发生冲突,那么我正好可以进行某项操作,如果要是发生冲突呢,那我就重试直到成功,乐观锁最常见的就是CAS。

    JAVA内部在实现原子操作的类时都应用到了CAS。

    3.2 CAS

    CAS是CompareAndSwap的缩写,即比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。

    CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作,大多数现代处理器都支持CAS指令。

    JDK中关于原子操作的类有,

    • 更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference

    • 更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

    • 更新引用类型:AtomicReference,AtomicMarkableReference,AtomicStampedReference

    • 原子更新字段类: AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater

    下面例子为AtomicInteger的基本用法,

    public class UseAtomicInt {
    	
    	static AtomicInteger ai = new AtomicInteger(10);
    	
        public static void main(String[] args) {
        	System.out.println(ai.getAndIncrement());//10--->11
        	System.out.println(ai.incrementAndGet());//11--->12--->out
        	System.out.println(ai.get());
        }
    }
    

    深入getAndIncrement的实现可以发现,内部调用了CAS进行value的更新,

       /**
         * Atomically adds the given value to the current value of a field
         * or array element within the given object {@code o}
         * at the given {@code offset}.
         *
         * @param o object/array to update the field/element in
         * @param offset field/element offset
         * @param delta the value to add
         * @return the previous value
         * @since 1.8
         */
        @HotSpotIntrinsicCandidate
        public final int getAndAddInt(Object o, long offset, int delta) {
            int v;
            do {
                v = getIntVolatile(o, offset);
            } while (!weakCompareAndSetInt(o, offset, v, v + delta));
            return v;
        }
    

    CAS在底层通过JNI调用系统的Atomic::cmpxchg方法,来进行value的比较与更新。

    对于CAS的实现,这依赖于CPU提供的特定指令,具体根据体系结构的不同还存在着明显区别。比如,x86 CPU 提供 cmpxchg 指令;而在精简指令集的体系架构中,则通常是靠一对儿指令(如“load and reserve”和“store conditional”)实现的,在大多数处理器上 CAS 都是个非常轻量级的操作,这也是其优势所在。

    3.3 CAS的问题

    CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。

    • ABA问题

    什么是ABA问题?ABA问题怎么解决?如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗?如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。
    Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。

    • 开销问题

    循环时间长开销很大:我们可以看到getAndAddInt方法执行时,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

    • 只能保证一个共享变量的原子操作

    只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。


    本文由『后端精进之路』原创,首发于博客 http://teckee.github.io/ , 转载请注明出处

    搜索『后端精进之路』关注公众号,立刻获取最新文章和价值2000元的BATJ精品面试课程

    后端精进之路.png

  • 相关阅读:
    Autho2----完整搭建实例
    详解SpringBoot应用跨域访问解决方案
    微信小程序后端开发流程
    前端必备 Nginx 配置
    后端必备 Nginx 配置
    关于spring boot集成MQTT
    Java 常用IO流操作详解
    spring boot 整合mybatis 的xml版本【包括逆向工程以及分页插件】
    实用 SQL 语句
    整理收集的一些常用java工具类
  • 原文地址:https://www.cnblogs.com/way2backend/p/12019872.html
Copyright © 2011-2022 走看看