java并发之CAS和AQS

CAS(Compare And Swap)

CAS

比较并交换,是解决多线程并发下使用锁造成的性能损耗的一种解决方式,CAS操作中包含三个操作数 – V(内存值) A(预期原值) B(新值-更新后的值) ,如果内存值 V = A 预期原值, 即标识这个值没有被修改过,则会把值更新为B, 否则不做任何处理;并返回最新的值;

在Java中是使用的 Unsafe 类提供了硬件级别的原子操作来实现的CAS,

AtomicInteger中的CAS使用

// 返回旧值,并设置新值, 这里是调用的 unsafe的getAndSetInt方法
   public final int getAndSet(int newValue) {
    
    
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }
    ...
 //  看下unsafe 的方法
    public final int getAndSetInt(Object var1, long var2, int var4) {
    
    
        int var5;
        do {
    
    
            var5 = this.getIntVolatile(var1, var2);
            // 这里 使用了 循环 进行 CAS操作
        } while(!this.compareAndSwapInt(var1, var2, var5, var4));

        return var5;
    }
   ...
   //通过cas原子性的将值设置为更新值,也是使用了 unsafe中的方法
    public final boolean compareAndSet(int expect, int update) {
    
    
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

一般情况下 在线程竞争不是很激烈的情况下 CAS比加锁的方式更高效,但是如果循环一直跳不出去 或者长时间不能设置成功,会浪费cpu

ABA问题

使用CAS会有ABA问题,就是 内存中的值被改过 但是又被改回来了, 当进行CAS操作的时候,内存的值和预期的值一样,但是其实是修改过后又被改回来的值; 这就是ABA问题;解决方案就是加个 版本号。
例如

A (内存值)-B(预期值) -C (新值)
变为 A1 - B1 -C1, 这样操作就是 比较A等于B的时候并且比较版本号是否一致,版本号一致则更新为C并加一个版本号,

AQS(AbstractQueuedSynchronizer)

AQS是JDK提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个内部框架

提供一个框架,用于实现依赖先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量,事件等)。 该类被设计为大多数类型的同步器的有用依据,这些同步器依赖于单个原子int值来表示状态。 子类必须定义改变此状态的受保护方法,以及根据该对象被获取或释放来定义该状态的含义。 给定这些,这个类中的其他方法执行所有排队和阻塞机制。 子类可以保持其他状态字段,但只以原子方式更新int使用方法操纵值getState() , setState(int)和compareAndSetState(int, int)被跟踪相对于同步。
子类应定义为非公共内部助手类,用于实现其封闭类的同步属性。 AbstractQueuedSynchronizer类不实现任何同步接口。 相反,它定义了一些方法,如acquireInterruptibly(int) ,可以通过具体的锁和相关同步器来调用适当履行其公共方法。
此类支持默认独占模式和共享模式。 当以独占模式获取时,尝试通过其他线程获取不能成功。 多线程获取的共享模式可能(但不需要)成功。 除了在机械意义上,这个类不理解这些差异,当共享模式获取成功时,下一个等待线程(如果存在)也必须确定它是否也可以获取。 在不同模式下等待的线程共享相同的FIFO队列。 通常,实现子类只支持这些模式之一,但是两者都可以在ReadWriteLock中发挥作用 。 仅支持独占或仅共享模式的子类不需要定义支持未使用模式的方法。
这个类定义的嵌套AbstractQueuedSynchronizer.ConditionObject可用于作为一类Condition由子类支持独占模式用于该方法的实施isHeldExclusively()份报告是否同步排他相对于保持在当前线程,方法release(int)与当前调用getState()值完全释放此目的,和acquire(int) ,给定此保存的状态值,最终将此对象恢复到其先前获取的状态。 AbstractQueuedSynchronizer方法将创建此类条件,因此如果不能满足此约束,请勿使用该约束。 AbstractQueuedSynchronizer.ConditionObject的行为当然取决于其同步器实现的语义。
该类为内部队列提供检查,检测和监控方法,以及条件对象的类似方法。 这些可以根据需要导出到类中,使用AbstractQueuedSynchronizer进行同步机制。
此类的序列化仅存储底层原子整数维持状态,因此反序列化对象具有空线程队列。 需要可序列化的典型子类将定义一个readObject方法,可以将其恢复为readObject时的已知初始状态。

state状态
AbstractQueuedSynchronizer维护了一个volatile 修饰的int 变量 state, 来表示同步状态。volatile保证了state多线程环境下的可见性。并且对state的操作都是原子性的

   /**
     * The synchronization state.
     */
    private volatile int state;
    
    protected final int getState() {
    
    
        return state;
    }
    protected final void setState(int newState) {
    
    
        state = newState;
    }
    protected final boolean compareAndSetState(int expect, int update) {
    
    
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

AQS的自定义共享方式

AQS分为 独占和 共享两个资源共享方式:
Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)
自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

源码
1. acquire(int)
acquire是一种以独占方式获取资源,如果获取到资源,线程直接返回运行,否则进入等待队列阻塞,线程可能会被反复阻塞和解除阻塞;整个过程是忽略线程中断的影响;该方式是独占模式下线程获取共享资源的顶层入口,获取到资源后,线程旧可以去执行其临界区代码;

    public final void acquire(int arg) {
    
    
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

tryAcquire(arg) 尝试去获取资源,如果成功则返回; 这个是需要自己实现的
addWaiter() 将线程加入到等待队列的尾部,并标记为独占模式
acquireQueued() 使线程在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
selfInterrupt(),将中断补上

主要逻辑: 通过调用子类实现的tryAcquire方法,该方法保证线程安全的获取同步状态,如果同步状态获取失败,则调用addWaiter() 以独占模式将线程加入到同步队列的尾部,最后调用acquireQueued() 方法,

猜你喜欢

转载自blog.csdn.net/xiaodujava/article/details/101526074