一、AQS是什么?为什么它这么重要?

想象一下你去银行办理业务,所有人都在无序地挤向柜台,那场面肯定乱成一锅粥。而在Java世界里,AQS(AbstractQueuedSynchronizer)就像银行的那个智能排队系统,它默默地维持着线程访问共享资源的秩序。

AQS是Java并发包里的一个基础框架,像ReentrantLock、CountDownLatch这些我们常用的工具,内部都是靠它实现的。它的核心思想很简单:当资源被占用时,其他线程会进入等待队列;当资源释放时,再按顺序唤醒等待的线程。

举个生活中的例子:AQS就像医院挂号系统。当所有医生都在忙时,新来的病人会取号排队(进入等待队列);当有医生空闲时,系统会按顺序叫下一个号(唤醒等待线程)。

二、AQS的核心实现原理

AQS内部维护了一个双向链表结构的等待队列,以及一个表示资源状态的volatile变量state。我们来看个最简单的实现示例:

// 技术栈:Java
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

// 自定义一个最简单的互斥锁
class SimpleLock extends AbstractQueuedSynchronizer {
    
    // 尝试获取锁
    protected boolean tryAcquire(int arg) {
        // CAS操作:如果state是0(未锁定),就设置为1(锁定)
        return compareAndSetState(0, 1);
    }
    
    // 尝试释放锁
    protected boolean tryRelease(int arg) {
        // 把state设置为0(未锁定)
        setState(0);
        return true;
    }
    
    // 判断是否持有锁
    protected boolean isHeldExclusively() {
        return getState() == 1;
    }
    
    // 加锁方法
    public void lock() {
        acquire(1);
    }
    
    // 解锁方法
    public void unlock() {
        release(1);
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        SimpleLock lock = new SimpleLock();
        
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("线程1获取锁");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("线程1释放锁");
            }
        }).start();
        
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("线程2获取锁");
            } finally {
                lock.unlock();
                System.out.println("线程2释放锁");
            }
        }).start();
    }
}

这个示例展示了AQS最基本的用法。当线程1获取锁后,线程2会进入等待队列,直到线程1释放锁后,线程2才会被唤醒。

三、AQS的两种模式

AQS支持两种资源共享模式:

  1. 独占模式(Exclusive):同一时刻只有一个线程能获取资源,比如ReentrantLock
  2. 共享模式(Shared):多个线程可以同时获取资源,比如Semaphore

我们来看个共享模式的例子:

// 技术栈:Java
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

// 自定义一个简单的信号量
class SimpleSemaphore extends AbstractQueuedSynchronizer {
    private final int maxPermits;
    
    public SimpleSemaphore(int maxPermits) {
        this.maxPermits = maxPermits;
        setState(maxPermits); // 初始化可用许可数
    }
    
    // 尝试获取许可(共享模式)
    protected int tryAcquireShared(int acquires) {
        for (;;) {
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 || 
                compareAndSetState(available, remaining)) {
                return remaining;
            }
        }
    }
    
    // 尝试释放许可
    protected boolean tryReleaseShared(int releases) {
        for (;;) {
            int current = getState();
            int next = current + releases;
            if (next > maxPermits) {
                throw new Error("超过最大许可数");
            }
            if (compareAndSetState(current, next)) {
                return true;
            }
        }
    }
    
    // 获取许可
    public void acquire() {
        acquireShared(1);
    }
    
    // 释放许可
    public void release() {
        releaseShared(1);
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        SimpleSemaphore semaphore = new SimpleSemaphore(3); // 允许3个线程同时访问
        
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                semaphore.acquire();
                try {
                    System.out.println(Thread.currentThread().getName() + "获取许可");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                    System.out.println(Thread.currentThread().getName() + "释放许可");
                }
            }).start();
        }
    }
}

在这个例子中,我们创建了一个最多允许3个线程同时访问的信号量。当第4个线程尝试获取许可时,它会进入等待状态,直到有线程释放许可。

四、AQS的高级特性

AQS还提供了一些高级功能,比如条件变量(Condition)。这就像是在银行排队时,专门为办理特定业务的人开设的专属窗口。我们来看个例子:

// 技术栈:Java
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;

class AdvancedLock extends AbstractQueuedSynchronizer {
    // 省略tryAcquire等基础方法...
    
    // 创建一个条件变量
    public Condition newCondition() {
        return new ConditionObject();
    }
}

public class ConditionExample {
    private static AdvancedLock lock = new AdvancedLock();
    private static Condition condition = lock.newCondition();
    private static boolean ready = false;
    
    public static void main(String[] args) {
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("等待线程:等待条件满足");
                while (!ready) {
                    condition.await(); // 等待条件
                }
                System.out.println("等待线程:条件已满足,继续执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }).start();
        
        new Thread(() -> {
            lock.lock();
            try {
                Thread.sleep(2000); // 模拟工作
                ready = true;
                condition.signal(); // 通知等待线程
                System.out.println("通知线程:条件已满足");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }).start();
    }
}

这个例子展示了如何使用AQS的条件变量功能。一个线程可以等待某个条件成立,另一个线程在条件成立时通知等待的线程。

五、AQS的应用场景

AQS在Java并发编程中无处不在,下面是一些典型应用:

  1. ReentrantLock:可重入锁,比synchronized更灵活
  2. Semaphore:控制同时访问资源的线程数量
  3. CountDownLatch:等待多个线程完成
  4. CyclicBarrier:让一组线程互相等待
  5. FutureTask:异步任务的基础实现

我们来看个CountDownLatch的例子:

// 技术栈:Java
import java.util.concurrent.CountDownLatch;

public class Race {
    public static void main(String[] args) throws InterruptedException {
        int runnerCount = 5;
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(runnerCount);
        
        for (int i = 0; i < runnerCount; i++) {
            new Thread(() -> {
                try {
                    System.out.println("选手" + Thread.currentThread().getId() + "准备就绪");
                    startSignal.await(); // 等待发令枪
                    System.out.println("选手" + Thread.currentThread().getId() + "开始跑步");
                    Thread.sleep((long)(Math.random() * 3000)); // 模拟跑步时间
                    System.out.println("选手" + Thread.currentThread().getId() + "到达终点");
                    doneSignal.countDown(); // 完成计数
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        
        Thread.sleep(2000); // 裁判准备
        System.out.println("发令枪响!");
        startSignal.countDown(); // 开始比赛
        
        doneSignal.await(); // 等待所有选手完成
        System.out.println("比赛结束!");
    }
}

这个例子模拟了跑步比赛,所有选手等待发令枪响,裁判等待所有选手完成比赛。

六、AQS的优缺点

优点:

  1. 高性能:相比传统的synchronized,AQS提供了更细粒度的控制
  2. 灵活性:可以基于AQS实现各种同步工具
  3. 公平性:支持公平和非公平两种模式

缺点:

  1. 复杂性:直接使用AQS需要理解其内部机制
  2. 容易出错:如果实现不当,可能导致死锁或资源泄漏

注意事项:

  1. 在tryAcquire/tryRelease等方法中不要执行耗时操作
  2. 确保锁一定会被释放,通常在finally块中释放
  3. 注意避免死锁,特别是可重入锁的多次获取

七、总结

AQS是Java并发包的基石,它通过一个精巧的等待队列机制,实现了各种同步工具。理解AQS的工作原理,能帮助我们更好地使用Java提供的并发工具,也能在需要时自定义适合特定场景的同步器。

虽然AQS的源码看起来有些复杂,但它的核心思想其实很简单:管理一个等待队列,通过CAS操作维护状态。在实际开发中,我们通常不需要直接继承AQS,而是使用基于它实现的工具类,如ReentrantLock、Semaphore等。

记住,并发编程的核心是管理对共享资源的访问,而AQS为我们提供了一套高效、灵活的解决方案。掌握了AQS,你就掌握了Java并发编程的关键。