`

【Java核心-进阶】CAS(compare-and-swap)

    博客分类:
  • Java
 
阅读更多

CAS简介

CAS(compare-and-swap)是一种对数据进行原子性操作的技术。
它提供了一系列操作指令用于读取数值,或并发修改。
它是Java并发中所谓 “lock-free” 机制的基础
CAS的底层依赖于CPU提供的指令。如,x86 CPU 的 cmpxchg

 

CAS使用方式

AtomicInteger

AtomicInteger使用了CAS技术,
它底层依赖于 Unsafe 对数据进行操作,
并使用 volatile 字段 value 记录数据,以保证可见性。

public class AtomicInteger extends Number implements java.io.Serializable {
  private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
  private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
  private volatile int value;

  public final int getAndIncrement() {
    return U.getAndAddInt(this, VALUE, 1);
  }
  ...
}
public final class Unsafe {
  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技术

注:此处仅说明较为底层的一些CAS使用方式。在实际开发中我们可以借助JDK并发包中一些比较高级的辅助类来实现原子化操作。如,ReentrantLock、Semaphore 等。
 

我们主要利用CAS的原子性操作来实现排他性地更改数据。
如,通过对某个字段进行原子操作来实现加锁与释放锁。

public class AtomicDataBlock {
  private volatile long lock;
  void acquireLock() {}
  void releaseLock() {}
}

 

直接调用 Unsafe 通常是不合适的。
这个类太底层,过于抽象,也发生过新版JDK不兼容的问题(部分方法被删除,甚至命名空间更改)。
 

JDK Atomic 包中提供了一些常用的原子性数据类型及相关辅助类。这些是比较好的选择。
如:AtomicBoolean、AtomicLong、AtomicReference、LongAdder、AtomicLongFieldUpdater

private final AtomicLong lock;
void acquireLock() {
  long threadId = Thread.currentThread().getId();
  while (!lock.compareAndSet(0, threadId)) {
    // 等待其它线程完成操作,然后重试
    ...
  }
}

 

从 Java 9 开始,可以使用 VarHandle 处理:

private volatile long lock;
private static final VarHandle HANDLE = MethodHandles.lookup()
    .findStaticVarHandle(AtomicDataBlock.class, "lock");
private void acquireLock() {
  long threadId = Thread.currentThread().getId;
  while (!HANDLE.compareAndSet(this, 0, threadId)) {
    // 等待其它线程完成操作,然后重试
    ...
  }
}

 

使用CAS的注意事项

避免过度自旋 —— 控制失败重试次数

使用CAS技术时,我们通常会结合失败重试机制。这隐含这一个假设:竞争情况很短暂,即使失败也很快能重试成功。
大多数应用场景中,这个假设确实成立。
但是一些特殊场景中,还是要考虑限制重试(自旋)的次数,以免过度消耗CPU。
【示例】AtomicInteger(Unsafe)中的失败重试:

public class AtomicInteger {
  public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
  }
  ...
}

public final class Unsafe {
  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;
  }
  ...
}

 

避免ABA问题 —— 使用 AtomicStampedReference

CAS机制在更新数值时会比较当前值,如果它与期望的值相同,则成功更新,否则失败。
(这就是 C(cmpare)的含义。)
 

但是在某些业务场景中,仅有目标字段的当前值符合期望并不足以保证后续业务操作的合理性。
如,目标字段发生了 A -> B -> A 的更新,后续操作看到A时会认为自己的操作是合理,而实际上B操作使得后续操作不合理。
 

为了应对这种情况,JDK提供了 AtomicStampedReference,通过增加类似版本号的 stamp 来避免ABA。
这是一个双重校验的机制。只有在 当前值(Reference)和 stamp 都符合预期时才会成功更新。

public class AtomicStampedReference {
  public boolean compareAndSet(V expectedReference, V newReference,
                               int expectedStamp, int newStamp);
  ...
}

 

AQS,AbstractQueuedSynchronizer —— 一个底层的原子化操作基础结构

AQS是JDK并发包中很多同步结构的基础。ReentrantLock、Semaphore、CountDownLatch等同步结构的内部、ThreadPoolExecutor中的Worker都基于AQS实现。
AQS的设计初衷是,从原理上,一种同步结构可以利用其它的结构实现。AQS内部就是包含了一些比较抽象的同步相关基础操作。
 

AQS内部大致可拆解为以下3部分:

# 一个 volatile 字段 state 表示状态

private volatile int state;

# 一个等待线程队列(先入先出)现实多线程间竞争和等待

# 各种基于CAS的基础操作方法,及同步结构具备的一些方法(如,acquirerelease)。

 

如,ReentrantLock 内部的 Sync 类继承自AQS,利用 state 字段来反映锁的持有情况。
lock 和 unlock 分别使用了 AQS 的 acquire 和 release 方法:

public void lock() {
  sync.acquire(1);
}

public void unlock() {
  sync.release(1);
}

 

AQS 的 acquire() 方法

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

AQS中的 tryAcquire() 方法仅仅是一个“空壳”(直接抛异常 UnsupportedOperationException)。
在 ReentrantLock 内部的 FairSync 和 NonfairSync 类分别实现了该方法的公平性和非公平性版本。
(创建ReentrantLock时可指定是否为公平锁。)
两者的主要区别在于,非公平版本检测到无其它线程占用时,会直接尝试获取锁,而不检查是否已有其它线程在等待

 

AQS 的 acquireQueued() 方法

在 acquire() 方法内部,如果 tryAcquire() 失败,就会进入排队竞争阶段。
当前线程会被包装为一个排他模式的节点(exclusive),由addWaiter方法将其添加到等待队列中。
 

此节点类Node包含一个 volatile 字段 waitStatus 来辅助实现线程间的同步。
如:其,。还有 Signal(-1)、Condition(-2)、Propagate(-3)等。

  • 初始值 0 表示普通同步节点
  • Cancelled 1 表示超时或中断,即节点(线程)不可用
  • Signal -1 表示节点(线程)释放资源时会唤醒后面的节点(线程)
  • Condition -2 表示节点(线程)处于 条件队列 中;当条件成立后,此节点会被添加到获取资源的队列中
  • Progagate -3 在共享模式下使用。如果节点(线程)获取到资源后还有足够的资源,它的后继节点(线程)会尝试获取

 

分享到:
评论

相关推荐

    Java-JUC-多线程 进阶

    CAS(Compare-And-Swap)是 Java 中的一种原子操作,用于比较和交换变量的值。CAS 可以用于解决多种并发编程问题,例如原子变量、锁机制等。 原子引用 原子引用是 Java 中的一种机制,用于描述原子的变量访问。...

    java-面试题合集,基础,进阶

    - 采用CAS(Compare and Swap)操作解决并发问题。 5. **对象访问模式**: - 直接指针访问。 - 手柄访问(Handle)。 6. **对象卸载条件**: - 类不再被引用。 - 加载该类的类加载器已经被卸载。 7. **Java...

    java进阶提高学习教程-14锁机制.pptx

    CAS 是 Compare And Swap,即比较和交换。CAS 使用一个期望值与一个变量的当前值比较,如果当前变量的值与期望值相等,则用一个新值来替换当前变量值。CAS 假设所有线程访问共享资源时不会出现冲突,线程不会出现...

    Java进阶知识点汇总.pdf

    - `AtomicInteger`使用了CAS(Compare and Swap)机制来实现原子更新操作,确保了线程安全性。 - CAS是一种乐观锁机制,通过比较预期值与当前值来决定是否进行更新,如果预期值与当前值相同则更新成功,否则失败并尝试...

    Java多线程之进阶篇(二).docx

    这些原子类使用了硬件级别的CAS(Compare and Swap)操作,这是一种低级的原语,它能比较并交换内存中的值,只有当预期的值与当前值相匹配时才会进行更新。这种方法比传统的锁定机制更加轻量级,可以降低锁竞争带来...

    java笔试题大全

    - CAS(Compare and Swap)与AQS(AbstractQueuedSynchronizer) 6. **Spring框架**: - Spring的核心模块(IoC, AOP) - Bean的生命周期与作用域 - 注解驱动开发(@Component, @Service, @Repository, @...

    深入JAVA虚拟机完整教程

    - CAS(Compare and Swap):无锁编程的一种原子操作,用于更新变量。 深入学习JVM可以帮助开发者更好地理解Java程序的运行机制,优化程序性能,解决内存溢出、死锁等问题,是每个Java程序员进阶的必经之路。通过...

    java面试精华5

    - ConcurrentHashMap在Java 1.8中采用了CAS(Compare and Swap)和synchronized进行同步,提高了并发性能。 - 1.7中的HashMap使用数组+链表,1.8引入了红黑树,当链表长度达到一定阈值时转换为红黑树,减少了哈希...

    Disruptor进阶.docx

    Disruptor摒弃了传统的锁机制,转而采用CPU级别的Compare and Swap (CAS)原语。CAS操作在硬件级别执行,不需要操作系统介入,因此避免了内核态切换和上下文切换,从而减少了开销。尽管这使得Disruptor的实现更为...

    java面试资料汇总 面试专题

    4. CAS(Compare and Swap)算法:无锁编程的基础,了解其工作原理及在并发中的应用。 四、Java性能优化 1. JMX(Java Management Extensions):监控和管理Java应用程序的工具。 2. JProfiler或VisualVM:性能分析...

    程序员成长学习要求

    - **CAS(Compare And Swap)**:了解CAS的基本原理及其在并发控制中的作用。 3. **Java并发工具包**:`java.util.concurrent` 包含了大量的并发工具类和接口,如`ExecutorService`、`Future`、`Callable`等,掌握...

    JAVA工程师面试题和一些经典题

    8. **CAS(Compare and Swap)**:无锁编程的一种实现,用于原子地更新变量。 在面试中,面试官可能会询问如何处理线程安全问题、如何优化线程池配置、如何避免死锁、对线程同步机制的理解等。因此,深入理解这些...

    【BAT必备】多线程面试题

    CAS(Compare and Swap)是一种无锁算法,在不使用锁的情况下实现多线程之间的变量同步。其工作原理是:比较变量的期望值和实际值是否一致,如果一致,则更新为新值;如果不一致,则不做任何操作。 #### 三、实战...

Global site tag (gtag.js) - Google Analytics