当前位置: 首页 > news >正文

Java CAS 与 AQS

1. CAS

1.1 CAS 原理

CAS(Compare-And-Swap)即比较并交换,是一种无锁算法,用于实现多线程环境下的原子操作。它是一种乐观锁的实现方式,其核心思想是:假设数据在没有被其他线程修改的情况下进行操作,如果发现数据被修改了,则重新尝试操作,直到成功为止。

CAS 操作包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。当且仅当内存位置 V 的值等于预期原值 A 时,才将该位置的值更新为新值 B;否则,不做任何操作。整个操作是原子性的,由硬件层面来保证。

在 Java 中,java.util.concurrent.atomic 包下的原子类就是基于 CAS 实现的,例如 AtomicIntegerAtomicLong 等。

1.2 使用示例

使用 AtomicInteger 进行自增操作

import java.util.concurrent.atomic.AtomicInteger;

public class CASAtomicIntegerExample {
    public static void main(String[] args) {
        // 创建一个 AtomicInteger 对象,初始值为 0
        AtomicInteger atomicInteger = new AtomicInteger(0);

        // 创建两个线程,每个线程对 atomicInteger 进行 1000 次自增操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                atomicInteger.incrementAndGet();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                atomicInteger.incrementAndGet();
            }
        });

        // 启动线程
        t1.start();
        t2.start();

        try {
            // 等待两个线程执行完毕
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出最终结果
        System.out.println("Final value: " + atomicInteger.get());
    }
}

解释

  • AtomicInteger 是 Java 提供的一个原子整数类,它内部使用 CAS 机制来保证对整数的操作是原子的。

  • incrementAndGet() 方法会原子地将 AtomicInteger 的值加 1,并返回加 1 后的值。

  • 在多线程环境下,多个线程可以同时调用 incrementAndGet() 方法,而不会出现数据不一致的问题。

1.3 CAS 的局限性

  • ABA 问题:CAS 操作只关注值是否相等,而不关心值的变化过程。如果一个值从 A 变为 B,再从 B 变回 A,CAS 操作会认为值没有发生变化,从而继续进行更新操作。可以使用 AtomicStampedReference 或 AtomicMarkableReference 来解决 ABA 问题。

  • 循环时间长开销大:如果 CAS 操作长时间不成功,会导致线程不断地进行自旋,从而消耗大量的 CPU 资源。

  • 只能保证一个共享变量的原子操作:CAS 只能保证对一个共享变量的原子操作,如果需要对多个共享变量进行原子操作,需要使用锁或其他同步机制。

2. AQS

AQS(AbstractQueuedSynchronizer)是 Java 并发包(java.util.concurrent)中用于构建锁和同步器的基础框架。许多 Java 并发工具类,如 ReentrantLockCountDownLatchSemaphore 等都是基于 AQS 实现的。

1.1 核心组成部分

  • 同步状态(state):一个 int 类型的变量,用于表示同步状态。不同的同步器可以根据这个状态来实现不同的同步逻辑。例如,在 ReentrantLock 中,state 为 0 表示锁未被持有,大于 0 表示锁已经被持有,并且数值表示重入的次数。

  • 队列(CLH 队列的变种):一个双向链表,用于管理那些获取同步状态失败的线程。当一个线程尝试获取同步状态失败时,会被封装成一个节点加入到队列的尾部,进入等待状态。当持有同步状态的线程释放状态后,会从队列的头部唤醒一个等待线程。

  • CAS(Compare-And-Swap)操作:用于保证对 state 变量的原子性修改。CAS 操作是一种无锁算法,通过比较内存中的值和预期值是否相等,如果相等则将内存中的值更新为新值。

1.2 工作流程

  • 获取同步状态:线程尝试通过 tryAcquire 方法获取同步状态,如果获取成功,则继续执行;如果获取失败,则将该线程封装成节点加入到等待队列中,并进入阻塞状态。

  • 释放同步状态:线程通过 tryRelease 方法释放同步状态,释放成功后,会从队列的头部唤醒一个等待线程。

  • 队列管理:当有新的线程获取同步状态失败时,会将其封装成节点加入到队列的尾部;当持有同步状态的线程释放状态后,会从队列的头部唤醒一个等待线程。

1.3 自定义同步器示例 

通过一个自定义同步器来演示 AQS 的使用,实现一个简单的独占锁。

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

// 自定义同步器
class MySync extends AbstractQueuedSynchronizer {
    // 尝试获取锁
    @Override
    protected boolean tryAcquire(int arg) {
        // 使用 CAS 操作尝试将状态从 0 变为 1
        if (compareAndSetState(0, 1)) {
            // 设置当前线程为独占线程
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    // 尝试释放锁
    @Override
    protected boolean tryRelease(int arg) {
        // 检查当前线程是否为独占线程
        if (getExclusiveOwnerThread() != Thread.currentThread()) {
            throw new IllegalMonitorStateException();
        }
        // 将状态设置为 0
        setState(0);
        // 清除独占线程
        setExclusiveOwnerThread(null);
        return true;
    }

    // 判断是否处于锁定状态
    @Override
    protected boolean isHeldExclusively() {
        return getState() == 1;
    }
}

// 自定义独占锁
class MyLock {
    private final MySync sync = new MySync();

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

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

    // 判断是否锁定
    public boolean isLocked() {
        return sync.isHeldExclusively();
    }
}

// 测试类
public class AQSExample {
    public static void main(String[] args) {
        MyLock lock = new MyLock();

        // 线程 1 尝试获取锁
        Thread thread1 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("Thread 1 acquired the lock.");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("Thread 1 released the lock.");
            }
        });

        // 线程 2 尝试获取锁
        Thread thread2 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("Thread 2 acquired the lock.");
            } finally {
                lock.unlock();
                System.out.println("Thread 2 released the lock.");
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

解释

  • MySync 类:继承自 AbstractQueuedSynchronizer,并重写了 tryAcquiretryRelease 和 isHeldExclusively 方法。

    • tryAcquire:尝试获取锁,使用 CAS 操作将状态从 0 变为 1,如果成功则将当前线程设置为独占线程。

    • tryRelease:尝试释放锁,检查当前线程是否为独占线程,如果是则将状态设置为 0,并清除独占线程。

    • isHeldExclusively:判断当前是否处于锁定状态。

  • MyLock 类:封装了 MySync 对象,提供了 lockunlock 和 isLocked 方法。

    • lock:调用 sync.acquire(1) 方法获取锁。

    • unlock:调用 sync.release(1) 方法释放锁。

    • isLocked:调用 sync.isHeldExclusively() 方法判断是否锁定。

  • AQSExample 类:创建了两个线程,分别尝试获取和释放锁,演示了自定义锁的使用。

注意事项

  • 在使用 AQS 时,需要根据具体需求重写 tryAcquiretryRelease 等方法,确保同步状态的正确管理。

  • AQS 提供了公平锁和非公平锁的实现,需要根据具体场景选择合适的锁策略。

相关文章:

  • 服务器主板可以单独升级吗?有什么影响?
  • 多模态模型:学习笔记(二)
  • MySQL 面试
  • Gemma2DecoderLayer 解析:Pre-FFW 和 Post-FFW LayerNorm 的作用
  • 【论文笔记-ECCV 2024】AnyControl:使用文本到图像生成的多功能控件创建您的艺术作品
  • VSCode+PlatformIO报错 找不到头文件
  • Zabbix告警分析新纪元:本地DeepSeek大模型实现智能化告警分析
  • 深度学习-133-LangGraph之应用实例(二)使用面向过程和面向对象的两种编程方式构建带记忆的聊天机器人
  • C#问题解决方案 --- 生成软件hash,生成文件hash
  • git merge -s ours ...的使用方法
  • 数据安全_笔记系列10:数据分类分级与保护策略详解
  • threejs:射线拾取封装
  • 计算机毕业设计 ——jspssm518Springboot 的影视影院订票选座管理系统
  • unity使用PICO Neo3开发,XR环境配置
  • 异常(2)
  • Java高频面试之SE-23
  • 27.[前端开发-JavaScript基础]Day04-函数基本使用-递归-变量作用域-函数式编程
  • 结构型模式 - 代理模式 (Proxy Pattern)
  • 利用python进行数据分析(重点、易忘点)---第八章数据规整:聚合、合并和重塑
  • Linux查看和处理文件内容
  • 北大青鸟培训/seo网站推广什么意思
  • 迷你主机做网站/手机管家一键优化
  • java网站搭建教程/网络推广软文怎么写
  • 政府网站建设升级白皮书/搜狗网
  • 网站加app建设/2024年的新闻时事热点论文
  • 企业为什么要培训/北京网站优化公司