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

CAS和AQS---java

CAS

CAS(Compare-And-Swap,比较并交换),是一种原子操作,它的基本原理是:

  • 比较:检查当前值是否等于预期值。
  • 交换:如果相等,则修改为新值;否则,不做任何修改。

CAS 主要依赖 CPU 提供的原子指令(如 x86 的 cmpxchg 指令)
原子指令(Atomic Instructions)是 CPU 提供的一种特殊指令,保证操作不会被中断,可以一次性完成读取、计算和写入,从而确保并发环境下的线程安全。

原子变量(Atomic 类)

Java 提供 java.util.concurrent.atomic 包,实现了一系列基于 CAS 的原子变量,如:

  • AtomicInteger:原子整型
  • AtomicLong:原子长整型
  • AtomicReference:原子引用类型
  • AtomicStampedReference:解决 ABA 问题的版本控制原子引用

常见方法

  1. get() 获取当前值
  2. set(int newValue) 直接修改值(非原子)
  3. incrementAndGet() 自增 1
  4. decrementAndGet() 自减 1
  5. compareAndSet(int expect, int update) CAS 操作

示例

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicExample {
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println("Thread " + Thread.currentThread().getName() + " count: " + count.incrementAndGet());
            }).start();
        }
    }
}

这里 incrementAndGet() 是原子操作,保证多个线程不会同时修改 count 而发生数据竞争。

CAS存在问题

  1. ABA 问题:如果一个变量从 A → B → A,CAS 可能误认为它没有变化。
    • 解决方案:使用AtomicStampedReference 记录版本号,避免误判。
  2. 自旋开销大:如果 CAS 频繁失败,线程会不断重试,消耗 CPU 资源。
  3. 只能更新单个变量:CAS 不能直接更新多个变量,需用 AtomicReference 组合多个变量。

AQS

AQS 通过状态变量 state(一个 volatile int 变量)和CLH 队列(FIFO 等待队列)来管理线程的同步访问。
AQS 是 Java 并发包的核心框架,用于构建锁和同步器(如 ReentrantLock、Semaphore)。

案例引入

public class Main {
    public static void main(String[] args) throws InterruptedException {
        int[] count = new int[]{1000};
        List<Thread> threads = new ArrayList<>();
        // Lock lock = new ReentrantLock();
        MyLock lock = new MyLock();
        for (int i = 0; i < 5; i++) {
            threads.add(new Thread(() -> {
                for (int i1 = 0; i1 < 5; i1++) {
                    // lock.lock();
                    count[0]--;
                }
                for (int i1 = 0; i1 < 5; i1++) {
                    // lock.unlock();
                }
            }));
        }
        for (Thread thread : threads) {
            thread.start();
        }

        for (Thread thread : threads) {
            thread.join();
        }
        System.out.println(count[0]);
    }
    
}

在主程序里,当把锁相关的代码注释后,就会造成并发安全的问题
那么我们能不能尝试造这样一把锁

锁的实现

public class MyLock {
	
	// 原子变量,指示是否拿到锁
    AtomicInteger state = new AtomicInteger(0);
	
	// 指示拥有锁的线程
    Thread owner = null;

    AtomicReference<Node> head = new AtomicReference<>(new Node());

    AtomicReference<Node> tail = new AtomicReference<>(head.get());


    void lock() {
        if (state.get() == 0) {
            if (state.compareAndSet(0, 1)) {
            	// 拿到锁
                System.out.println(Thread.currentThread().getName() + "直接拿到锁");
                owner = Thread.currentThread();
                return;
            }
        } 
        else {
            if (owner == Thread.currentThread()) {
                System.out.println(Thread.currentThread().getName() + " 拿到了重入锁,当前重入次数为" + state.incrementAndGet());
                return;
            }
        }
		// 没有拿到锁,把自己加入到阻塞链表
        Node current = new Node();
        current.thread = Thread.currentThread();
        while (true) {
        	// 不断尝试获取当前链表尾结点,把自己加入尾结点后面
            Node currentTail = tail.get();
            if (tail.compareAndSet(currentTail, current)) {
            	// 自己成为尾结点,修改链表结构
                System.out.println(Thread.currentThread().getName() + "加入到了链表尾");
                current.pre = currentTail;
                currentTail.next = current;
                break;
            }
        }
        while (true) {
        	// 尝试自己自己唤醒自己一次(防止自己还没加入链表,拥有锁的线程已经结束了唤醒函数,这时就没有人再去唤醒了)
            if (current.pre == head.get() && state.compareAndSet(0, 1)) {
                owner = Thread.currentThread();
                head.set(current);
                current.pre.next = null;
                current.pre = null;
                System.out.println(Thread.currentThread().getName() + "被唤醒之后,拿到锁");
                return;
            }
            // 阻塞当前线程
            LockSupport.park();

        }

    }

    void unlock() {
        if (Thread.currentThread() != this.owner) {
            throw new IllegalStateException("当前线程并没有锁,不能解锁");
        }
        int i = state.get();
        if (i > 1) {
            state.set(i - 1);
            System.out.println(Thread.currentThread().getName() + "解锁了重入锁,重入锁剩余次数" + (i - 1));
            return;
        }
        if (i <= 0) {
            throw new IllegalStateException("重入锁解锁错误!");
        }
        Node headNode = head.get();
        Node next = headNode.next;
        state.set(0);
        if (next != null) {
            System.out.println(Thread.currentThread().getName() + "唤醒了" + next.thread.getName());
            // 唤醒下一个线程结点
            LockSupport.unpark(next.thread);
        }
    }

    class Node {
        Node pre;
        Node next;
        Thread thread;
    }

}

参考:
https://blog.csdn.net/qq_35923846/article/details/140186945?ops_request_misc=%257B%2522request%255Fid%2522%253A%25227793918c79a5db9a8efbab96fc0d36d2%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=7793918c79a5db9a8efbab96fc0d36d2&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_click~default-2-140186945-null-null.142v101pc_search_result_base1&utm_term=aqs&spm=1018.2226.3001.4187
https://github.com/implement-study/lock_demo/tree/main/src/main/java/tech/insight
https://blog.csdn.net/u014745069/article/details/117875073?ops_request_misc=%257B%2522request%255Fid%2522%253A%25227793918c79a5db9a8efbab96fc0d36d2%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=7793918c79a5db9a8efbab96fc0d36d2&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-4-117875073-null-null.142v101pc_search_result_base1&utm_term=aqs&spm=1018.2226.3001.4187

相关文章:

  • “你使用的是不受支持的命令行标志:--no-sandbox。这会带来稳定性和安全风险。”提示解决方法
  • Spring的MutipartFile 会直接将流转成文件存放在临时目录嘛?
  • MySQL表约束的种类与应用
  • AI 时代下,操作系统如何进化与重构?
  • 头歌实验---C/C++程序设计:实验2:顺序结构程序设计
  • C#开发——时间间隔类TimSpan
  • 【01游戏——DFS】
  • 每天一个Flutter开发小项目 (6) : 表单与验证的专业实践 - 构建预约应用
  • 常见锁类型介绍
  • iOS自归因详细介绍
  • undo log、redo log、bin log是什么
  • Springboot 3项目整合Knife4j接口文档(接口分组详细教程)
  • 基于Spring Boot的二手物品交易平台设计与实现(LW+源码)
  • Meta最新研究:从单张照片到3D数字人的革命性突破
  • 开放标准(RFC 7519):JSON Web Token (JWT)
  • 深入解析Crawl4AI:为AI应用量身定制的高效开源爬虫框架
  • Day7、Vue3 组件通信技术
  • Vue3父组件访问子组件方法与属性完全指南
  • JBoltAI_SpringBoot 资源管理:打造一站式 AI 资源管理平台
  • LinuxNvidia显卡驱动, cuda工具包,驱动包版本记录
  • 大连seo建站公司/漳州网络推广
  • 学习做网站只学过c/360优化大师安卓手机版下载安装
  • 医院导航网站怎么做/百度互联网营销顾问
  • css做电影海报网站设计/2022年新闻摘抄十条
  • 中国最近新闻消息/点击宝seo
  • 企顺网网站建设/网站建设公司地址在哪