【Java并发】【AQS】适合初学者体质的AQS入门

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD

🔥 2025本人正在沉淀中... 博客更新速度++

👍 欢迎点赞、收藏、关注,跟上我的更新节奏

📚欢迎订阅专栏,专栏名《在2B工作中寻求并发是否搞错了什么》

前言

AQS这是灰常重要的哈,很多JUC下的框架的核心,那都是我们的AQS,所以这里,我们直接开始先研究AQS。

那说到研究AQS,那我们应该,使用开始说起🤓

入门

什么是AQS?

AQS(AbstractQueuedSynchronizer)是 Java 并发编程中一个非常重要的基础框架,它是 Java 并发包(java.util.concurrent)中许多同步工具(如 ReentrantLockSemaphoreCountDownLatch 等)的核心实现。

AQS 通过 共享资源状态(state)线程等待队列(CLH 队列) 实现同步机制,核心逻辑是:

  1. 原子性管理状态(state) :通过 volatile int state 表示资源状态(例如锁是否被持有、信号量剩余数量)。不同场景下的含义:

    • ReentrantLockstate=0 表示未加锁,state>0 表示锁被重入的次数。
    • Semaphorestate 表示剩余许可证数量。
    • CountDownLatchstate 表示剩余未完成的计数。
  2. 线程排队与唤醒:未获取资源的线程进入队列等待,资源释放时按策略唤醒队列中的线程。

    • 一个双向链表实现的 FIFO 队列,存储等待资源的线程。
    • 每个节点(Node)封装一个线程及其等待状态(如是否被取消)。

AQS 的两种模式

1. 独占模式(Exclusive)

同一时刻只有一个线程能获取资源 ,如 ReentrantLock。.

java 复制代码
boolean tryAcquire(int arg)   // 尝试获取资源(需子类实现)
boolean tryRelease(int arg)   // 尝试释放资源(需子类实现)

2. 共享模式(Shared)

多个线程可以同时获取资源 ,如 SemaphoreCountDownLatch

java 复制代码
int tryAcquireShared(int arg) // 尝试获取共享资源(返回剩余可用数量)
boolean tryReleaseShared(int arg) // 尝试释放共享资源

为什么要用AQS?

因为方便!!

如果没有AQS,我们将要自己解决以下问题:

  1. 线程的阻塞和唤醒:你需要自己实现一个队列来管理等待的线程,并确保线程可以公平地获取资源。
  2. 状态管理:你需要自己管理同步状态,并确保状态的操作是线程安全的。
  3. 避免竞争条件:你需要使用 CAS 操作或其他同步机制来避免竞争条件。
  4. 性能优化:你需要优化线程的阻塞和唤醒机制,以减少上下文切换的开销。

使用AQS

继承AbstractQueuedSynchronizer类,重写方法,这里有没有uu感受到模版方法设计模式呢?

不了解模板方法的uu,可以看看这篇:【设计模式】【行为型模式】模板方法模式(Template Method)-CSDN博客

独占模式

  • acquire(int arg) :获取资源的模板方法。tryAcquire(arg) :由子类重写(如 ReentrantLock 定义如何获取锁)。
  • release(int arg) :释放资源的模板方法。tryRelease(arg) :由子类重写(如 ReentrantLock 定义如何释放锁)。
java 复制代码
// 需要我们重写获取资源 
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

// 占有资源的模版方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&      // 子类实现:尝试获取资源
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // AQS 实现:排队等待
        selfInterrupt();
}

// 需要我们重写释放资源
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

// 释放资源的模板方法
public final boolean release(int arg) {
    if (tryRelease(arg)) {       // 子类实现:尝试释放资源
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);   // AQS 实现:唤醒后续线程
        return true;
    }
    return false;
}

共享模式

  • acquireShared(int arg) releaseShared(int arg)
    模板方法逻辑类似,调用子类实现的 tryAcquireShared()tryReleaseShared()
java 复制代码
// 共享模式获取资源的入口方法(模板方法)
public final void acquireShared(int arg) {
    // 调用子类实现的 tryAcquireShared 方法
    if (tryAcquireShared(arg) < 0)
        // AQS 实现的共享模式排队逻辑
        doAcquireShared(arg);
}

// 共享模式释放资源的入口方法(模板方法)
public final boolean releaseShared(int arg) {
    // 调用子类实现的 tryReleaseShared 方法
    if (tryReleaseShared(arg)) {
        // AQS 实现的共享模式释放逻辑
        doReleaseShared();
        return true;
    }
    return false;
}

上面介绍的非常重要,不然会有略微影响,看下面的内容。

下面会使用到AQS的独占模式共享模式, 来简单实现JUC框架的一些功能,跟上主播的节奏😘

实现一个简单的独占锁

独占锁:用于确保同一时刻只有一个线程可以获取资源。

继承 AbstractQueuedSynchronizer 并重写 tryAcquire/tryRelease 方法。

java 复制代码
public class SimpleLock {
    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            return compareAndSetState(0, 1); // 尝试获取锁
        }

        @Override
        protected boolean tryRelease(int arg) {
            setState(0); // 释放锁
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1; // 判断锁是否被持有
        }
    }

    private final Sync sync = new Sync();

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

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

    public boolean isLocked() {
        return sync.isHeldExclusively(); // 检查锁状态
    }
}

使用

java 复制代码
public class SimpleLockExample {
    private static final SimpleLock lock = new SimpleLock();

    public static void main(String[] args) {
        Runnable task = () -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " acquired the lock");
                Thread.sleep(1000); // 模拟操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println(Thread.currentThread().getName() + " released the lock");
            }
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);

        t1.start();
        t2.start();
    }
}

实现一个信号量(Semaphore)

信号量用于控制同时访问某个资源的线程数量。

java 复制代码
public class SimpleSemaphore {
    private static class Sync extends AbstractQueuedSynchronizer {
        Sync(int permits) {
            setState(permits); // 初始化信号量许可数
        }

        @Override
        protected int tryAcquireShared(int acquires) {
            for (;;) {
                int available = getState(); // 当前可用的许可数
                int remaining = available - acquires; // 获取后的剩余许可数
                if (remaining < 0 || compareAndSetState(available, remaining)) {
                    return remaining; // 返回剩余许可数
                }
            }
        }

        @Override
        protected boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState(); // 当前可用的许可数
                int next = current + releases; // 释放后的许可数
                if (compareAndSetState(current, next)) {
                    return true; // 释放成功
                }
            }
        }
    }

    private final Sync sync;

    public SimpleSemaphore(int permits) {
        if (permits < 0) {
            throw new IllegalArgumentException("Permits must be non-negative");
        }
        this.sync = new Sync(permits);
    }

    public void acquire() {
        sync.acquireShared(1); // 获取一个许可
    }

    public void release() {
        sync.releaseShared(1); // 释放一个许可
    }
}

使用

java 复制代码
public class SemaphoreExample {
    private static final SimpleSemaphore semaphore = new SimpleSemaphore(2); // 允许2个线程同时访问

    public static void main(String[] args) {
        Runnable task = () -> {
            semaphore.acquire();
            try {
                System.out.println(Thread.currentThread().getName() + " acquired the permit");
                Thread.sleep(1000); // 模拟操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
                System.out.println(Thread.currentThread().getName() + " released the permit");
            }
        };

        for (int i = 0; i < 4; i++) {
            new Thread(task).start();
        }
    }
}

实现一个倒计时门闩(CountDownLatch)

倒计时门闩用于让一个或多个线程等待其他线程完成操作。

java 复制代码
public class SimpleCountDownLatch {
    private static class Sync extends AbstractQueuedSynchronizer {
        Sync(int count) {
            setState(count); // 初始化计数器
        }

        @Override
        protected int tryAcquireShared(int acquires) {
            return getState() == 0 ? 1 : -1; // 如果计数器为0,返回1(成功),否则返回-1(失败)
        }

        @Override
        protected boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                if (current == 0) {
                    return false; // 计数器已经为0,无法再减少
                }
                int next = current - 1;
                if (compareAndSetState(current, next)) {
                    return next == 0; // 返回是否计数器减到0
                }
            }
        }
    }

    private final Sync sync;

    public SimpleCountDownLatch(int count) {
        this.sync = new Sync(count);
    }

    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1); // 等待计数器减到0
    }

    public void countDown() {
        sync.releaseShared(1); // 计数器减1
    }
}

我们重写的tryAcquireShared被调用的位置。

java 复制代码
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}


public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

使用

arduino 复制代码
public class CountDownLatchExample {
    private static final SimpleCountDownLatch latch = new SimpleCountDownLatch(3);

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            System.out.println(Thread.currentThread().getName() + " is running");
            latch.countDown(); // 计数器减1
        };

        for (int i = 0; i < 3; i++) {
            new Thread(task).start();
        }

        latch.await(); // 等待计数器减到0
        System.out.println("All tasks are done!");
    }
}

实现条件变量(Condition)

  • 锁与条件的绑定

    • 条件变量 Condition 必须与锁(AQS)绑定,因为 await()signal() 必须在持有锁的情况下调用。
  • 线程状态管理

    • AQS 内部通过 ConditionObject 管理条件队列,通过 acquirerelease 管理同步队列。
  • 条件队列与同步队列的交互

    • 当线程调用 await() 时,它会被放入条件队列并释放锁。
    • 当其他线程调用 signal() 时,条件队列中的线程会被移动到同步队列,等待重新获取锁。
java 复制代码
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;

public class SimpleLockWithCondition {
    // 继承 AQS 并实现一个简单的独占锁
    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            return compareAndSetState(0, 1); // 尝试获取锁
        }

        @Override
        protected boolean tryRelease(int arg) {
            setState(0); // 释放锁
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1; // 锁是否被持有
        }

        // 直接使用 AQS 内置的 ConditionObject
        public Condition newCondition() {
            return new ConditionObject();
        }
    }

    private final Sync sync = new Sync();

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

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

    public Condition newCondition() {
        return sync.newCondition(); // 返回 AQS 的条件队列
    }
}

测试使用

java 复制代码
public class ConditionExample {
    private static final SimpleLockWithCondition lock = new SimpleLockWithCondition();
    private static final Condition condition = lock.newCondition();
    private static boolean flag = false;

    public static void main(String[] args) {
        // 线程1:等待条件满足
        Thread waiter = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("Thread-1: 等待条件满足...");
                while (!flag) {
                    condition.await(); // 释放锁并等待(AQS 内部管理条件队列)
                }
                System.out.println("Thread-1: 条件已满足!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        // 线程2:修改条件并唤醒等待线程
        Thread signaler = new Thread(() -> {
            lock.lock();
            try {
                Thread.sleep(1000); // 模拟耗时操作
                flag = true;
                System.out.println("Thread-2: 条件已修改,唤醒等待线程");
                condition.signalAll(); // 唤醒所有等待线程(AQS 内部将线程从条件队列移到同步队列)
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        waiter.start();
        signaler.start();
    }
}

后话

怎么样,是不是对AQS有点略微的印象了呢?知道它能怎么用的就行了。

什么就结束了?不对的。根据主播以往的规律,下一篇就是原理or源码了。

点上关注,主播马上回来!!!

相关推荐
Code哈哈笑16 分钟前
【JavaEE】快速上手JSON:构建高效 Java 后端数据桥梁,开启交互新篇,以 @RequestBody 为引的探索之旅
java·java-ee·json
Asthenia041218 分钟前
MyBatis-Plus 之逻辑删除:@TableLogic与全局配置字段逻辑删除之优势与劣势
后端
青云交27 分钟前
Java 大视界 -- Java 大数据在智慧交通自动驾驶仿真与测试数据处理中的应用(136)
java·大数据·自动驾驶·数据存储·仿真·智慧交通·测试数据处理
糖心何包蛋爱编程27 分钟前
(四)Reactor核心-前置知识3
java·开发语言·经验分享·响应式编程·streamapi
Trouvaille ~33 分钟前
【Java篇】一气化三清:类的实例化与封装的智慧之道
java·开发语言·类和对象·javase·编程规范·基础入门·软件包
纠结哥_Shrek1 小时前
Java 的 正则表达式
java·开发语言·正则表达式
青云交1 小时前
Java 大视界 -- 基于 Java 的大数据分布式存储系统的数据备份与恢复策略(139)
java·大数据·分布式·数据恢复·数据备份·分布式存储·并行处理
飞翔中文网1 小时前
Java设计模式之模板方法模式
java·设计模式
Lizhihao_1 小时前
JAVA-多线程join()等待一个线程
java·开发语言
一个热爱生活的普通人1 小时前
Gin与数据库:GORM集成实战
后端·go