cas客户端登陆状态不同步_JAVA 并发编程: CAS 和 AQS

a95f0b0f28fcc05ed0e10f1efff05721.png

说起 JAVA 并发编程,就不得不聊聊 CAS (Compare And Swap) 和 AQS 了 (AbstractQueuedSynchronizer)。


CAS(Compare And Swap)

什么是 CAS

CAS (Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值 (B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了 “我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

在 JAVA 中,sun.misc.Unsafe 类提供了硬件级别的原子操作来实现这个 CAS。 java.util.concurrent 包下的大量类都使用了这个 Unsafe.java 类的 CAS 操作。至于 Unsafe.java 的具体实现这里就不讨论了。

CAS 典型应用

java.util.concurrent.atomic 包下的类大多是使用 CAS 操作来实现的 (eg. AtomicInteger.java,AtomicBoolean,AtomicLong)。下面以 AtomicInteger.java 的部分实现来大致讲解下这些原子类的实现。

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();

    private volatile int value;// 初始int大小
    // 省略了部分代码...

    // 带参数构造函数,可设置初始int大小
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
    // 不带参数构造函数,初始int大小为0
    public AtomicInteger() {
    }

    // 获取当前值
    public final int get() {
        return value;
    }

    // 设置值为 newValue
    public final void set(int newValue) {
        value = newValue;
    }

    //返回旧值,并设置新值为 newValue
    public final int getAndSet(int newValue) {
        /**
        * 这里使用for循环不断通过CAS操作来设置新值
        * CAS实现和加锁实现的关系有点类似乐观锁和悲观锁的关系
        * */
        for (;;) {
            int current = get();
            if (compareAndSet(current, newValue))
                return current;
        }
    }

    // 原子的设置新值为update, expect为期望的当前的值
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    // 获取当前值current,并设置新值为current+1
    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

    // 此处省略部分代码,余下的代码大致实现原理都是类似的
}


一般来说在竞争不是特别激烈的时候,使用该包下的原子操作性能比使用 synchronized 关键字的方式高效的多 (查看 getAndSet(),可知如果资源竞争十分激烈的话,这个 for 循环可能换持续很久都不能成功跳出。不过这种情况可能需要考虑降低资源竞争才是)。 在较多的场景我们都可能会使用到这些原子类操作。一个典型应用就是计数了,在多线程的情况下需要考虑线程安全问题。通常第一映像可能就是:

public class Counter {
    private int count;
    public Counter(){}
    public int getCount(){
        return count;
    }
    public void increase(){
        count++;
    }
}


上面这个类在多线程环境下会有线程安全问题,要解决这个问题最简单的方式可能就是通过加锁的方式,调整如下:

public class Counter {
    private int count;
    public Counter(){}
    public synchronized int getCount(){
        return count;
    }
    public synchronized void increase(){
        count++;
    }
}


这类似于悲观锁的实现,我需要获取这个资源,那么我就给他加锁,别的线程都无法访问该资源,直到我操作完后释放对该资源的锁。我们知道,悲观锁的效率是不如乐观锁的,上面说了 Atomic 下的原子类的实现是类似乐观锁的,效率会比使用 synchronized 关系字高,推荐使用这种方式,实现如下:

public class Counter {
    private AtomicInteger count = new AtomicInteger();
    public Counter(){}
    public int getCount(){
        return count.get();
    }
    public void increase(){
        count.getAndIncrement();
    }
}

AQS(AbstractQueuedSynchronizer)

什么是 AQS

AQS(AbstractQueuedSynchronizer),AQS 是 JDK 下提供的一套用于实现基于 FIFO 等待队列的阻塞锁和相关的同步器的一个同步框架。这个抽象类被设计为作为一些可用原子 int 值来表示状态的同步器的基类。如果你有看过类似 CountDownLatch 类的源码实现,会发现其内部有一个继承了 AbstractQueuedSynchronizer 的内部类 Sync 。可见 CountDownLatch 是基于 AQS 框架来实现的一个同步器。类似的同步器在 JUC 下还有不少。(eg. Semaphore )

AQS 用法

如上所述,AQS 管理一个关于状态信息的单一整数,该整数可以表现任何状态。比如, Semaphore 用它来表现剩余的许可数,ReentrantLock 用它来表现拥有它的线程已经请求了多少次锁;FutureTask 用它来表现任务的状态 (尚未开始、运行、完成和取消)

To use this class as the basis of a synchronizer, redefine the
* following methods, as applicable, by inspecting and/or modifying
* the synchronization state using {@link #getState}, {@link
* #setState} and/or {@link #compareAndSetState}:
*
* <ul>
* <li> {@link #tryAcquire}
* <li> {@link #tryRelease}
* <li> {@link #tryAcquireShared}
* <li> {@link #tryReleaseShared}
* <li> {@link #isHeldExclusively}
* </ul>


如 JDK 的文档中所说,使用 AQS 来实现一个同步器需要覆盖实现如下几个方法,并且使用 getState,setState,compareAndSetState 这几个方法来设置获取状态

  1. boolean tryAcquire(int arg)
  2. boolean tryRelease(int arg)
  3. int tryAcquireShared(int arg)
  4. boolean tryReleaseShared(int arg)
  5. boolean isHeldExclusively()

以上方法不需要全部实现,根据获取的锁的种类可以选择实现不同的方法,支持独占 (排他) 获取锁的同步器应该实现 tryAcquiretryReleaseisHeldExclusively 而支持共享获取的同步器应该实现 tryAcquireSharedtryReleaseSharedisHeldExclusively。下面以 CountDownLatch 举例说明基于 AQS 实现同步器,CountDownLatch 用同步状态持有当前计数,countDown 方法调用 release 从而导致计数器递减;当计数器为 0 时,解除所有线程的等待;await 调用 acquire,如果计数器为 0,acquire 会立即返回,否则阻塞。通常用于某任务需要等待其他任务都完成后才能继续执行的情景。源码如下:

public class CountDownLatch {
    /**
     * 基于AQS的内部Sync
     * 使用AQS的state来表示计数count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            // 使用AQS的getState()方法设置状态
            setState(count);
        }

        int getCount() {
            // 使用AQS的getState()方法获取状态
            return getState();
        }

        // 覆盖在共享模式下尝试获取锁
        protected int tryAcquireShared(int acquires) {
            // 这里用状态state是否为0来表示是否成功,为0的时候可以获取到返回1,否则不可以返回-1
            return (getState() == 0) ? 1 : -1;
        }

        // 覆盖在共享模式下尝试释放锁
        protected boolean tryReleaseShared(int releases) {
            // 在for循环中Decrement count直至成功;
            // 当状态值即count为0的时候,返回false表示 signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

    private final Sync sync;

    // 使用给定计数值构造CountDownLatch
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    // 让当前线程阻塞直到计数count变为0,或者线程被中断
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    // 阻塞当前线程,除非count变为0或者等待了timeout的时间。当count变为0时,返回true
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    // count递减
    public void countDown() {
        sync.releaseShared(1);
    }

    // 获取当前count值
    public long getCount() {
        return sync.getCount();
    }

    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

本文大致就讲了这些东西,有些地方说的也不是特别好。也有不全的地方,AQS 的东西还是有不少的,建议大家自己去看 JUC 下的各个类的实现,配合《JAVA 并发编程实践》这本书,相信是恩呢挂钩看明白的,从而得到更深刻的理解。


相关文章

  • 记一次 Gradle Build 问题分析
  • 定位 CPU 异常抖动 ---tomcat 热部署的坑
  • 请求响应慢问题排查
  • Jasperreports OOM 问题处理
  • 微信公众平台 SDK JAVA 实现 WeChat-SDK
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值