深入解析 AQS:同步队列与条件队列原理,独占与共享模式的实现与应用

1. AQS(AbstractQueuedSynchronizer)的同步队列和条件队列原理

同步队列(Sync Queue)

同步队列是 AQS 的核心部分,管理着线程的排队和唤醒。在并发环境中,当一个线程请求获取锁(或者同步状态)失败时,它会被放入同步队列中等待,直到锁可用时重新被唤醒。

核心原理:

  • 双向链表结构:AQS 的同步队列是一个双向链表,头节点通常是持有锁的线程,后续节点是等待获取锁的线程。
  • 自旋和阻塞机制:当某个线程获取锁失败时,它会自旋一段时间(忙等待),如果锁仍然不可用,则会被挂起并放入队列中。线程被唤醒时重新尝试获取锁。
  • 锁的释放和唤醒机制:当持有锁的线程释放锁时,AQS 会唤醒队列中的下一个等待线程,让它尝试获取锁。
条件队列(Condition Queue)

条件队列用于实现 Condition 的等待和通知机制。在 Java 的并发库中,Condition 对象可以用来管理线程的等待条件,当线程等待某个条件时,它会被放入条件队列中,直到被其他线程唤醒。

核心原理:

  • 单向链表结构:条件队列是一个单向链表结构,存储的是等待特定条件的线程。
  • 等待和唤醒机制:线程调用 await() 方法时,会进入条件队列,释放当前持有的锁并进入阻塞状态。其他线程调用 signal()signalAll() 方法时,会将条件队列中的线程移动到同步队列中,重新尝试获取锁。
两者的关系:
  • 同步队列是管理线程获取同步资源的主要队列,而条件队列则用于管理线程的等待条件。
  • 当条件满足时,线程会从条件队列转移到同步队列中,等待锁的获取。

2. AQS 的独占模式和共享模式

AQS 支持两种锁的获取模式:独占模式共享模式

独占模式(Exclusive Mode)

在独占模式下,只有一个线程能获取同步状态(例如锁),其他线程必须等待。当当前线程释放同步状态后,其他线程才能尝试获取。

  • 典型例子ReentrantLock 的独占锁。

  • 工作原理

    1. 当线程获取同步状态(如锁)时,如果成功则进入临界区。
    2. 如果失败,线程进入同步队列等待。
    3. 当持有锁的线程释放锁时,队列中的下一个线程被唤醒并尝试获取锁。
  • 适用场景:独占模式适用于所有只能有一个线程访问临界区的场景,比如互斥锁。

共享模式(Shared Mode)

在共享模式下,多个线程可以同时获取同步状态(如共享资源),这意味着允许多个线程并发执行。

  • 典型例子CountDownLatchSemaphore 的共享锁。

  • 工作原理

    1. 当多个线程试图获取同步状态时,如果资源充足,多个线程可以同时获得访问权限。
    2. 如果资源不足,部分线程进入同步队列等待。
    3. 当资源释放时,队列中的等待线程可以被唤醒并继续尝试获取资源。
  • 适用场景:共享模式适用于需要允许多个线程同时访问共享资源的场景,例如限流器、计数器等。

3. AQS 同步队列与条件队列原理结合实例

示例:简易的 ReentrantLock 实现
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class SimpleReentrantLock {
    
    
    private final Sync sync;

    // AQS 内部类实现
    private static class Sync extends AbstractQueuedSynchronizer {
    
    
        // 尝试获取锁,独占模式
        @Override
        protected boolean tryAcquire(int acquires) {
    
    
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
    
    
                if (compareAndSetState(0, acquires)) {
    
    
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } else if (current == getExclusiveOwnerThread()) {
    
    
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        // 尝试释放锁
        @Override
        protected boolean tryRelease(int releases) {
    
    
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
    
    
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        protected boolean isHeldExclusively() {
    
    
            return getState() != 0 && getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
    
    
            return new ConditionObject();
        }
    }

    public SimpleReentrantLock() {
    
    
        sync = new Sync();
    }

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

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

    public boolean isLocked() {
    
    
        return sync.isHeldExclusively();
    }

    public java.util.concurrent.locks.Condition newCondition() {
    
    
        return sync.newCondition();
    }
}

public class LockTest {
    
    
    public static void main(String[] args) {
    
    
        final SimpleReentrantLock lock = new SimpleReentrantLock();

        Runnable task = () -> {
    
    
            lock.lock();
            try {
    
    
                System.out.println(Thread.currentThread().getName() + " acquired lock");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                System.out.println(Thread.currentThread().getName() + " releasing lock");
                lock.unlock();
            }
        };

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

        t1.start();
        t2.start();
    }
}
代码解释:
  1. Sync:基于 AQS 实现,提供了 tryAcquiretryRelease 方法,控制锁的获取和释放。
  2. 独占模式:在 tryAcquire 中,只有当前线程可以获取锁,其他线程必须等待。
  3. 双向同步队列:如果线程获取锁失败,会被加入同步队列,等待唤醒。
  4. 条件队列newCondition 方法创建条件队列,用于实现等待通知机制。
运行结果:

在这个简易的 ReentrantLock 实现中,当 Thread t1 持有锁时,Thread t2 会进入同步队列等待,直到 Thread t1 释放锁后才会被唤醒。

4. AQS 的使用场景与问题解决

使用场景:
  • 互斥锁:如 ReentrantLock,用于确保同一时刻只有一个线程进入临界区。
  • 共享锁:如 Semaphore,允许多个线程同时访问共享资源。
  • 同步器:如 CountDownLatch,用于协调多个线程之间的同步。
  • 条件等待:如 Condition,用于实现复杂的线程间通信和条件等待。
业务场景:

可以在需要高效管理并发任务和线程控制的场景下使用 AQS,如任务调度器、流量限流、资源访问控制等。

猜你喜欢

转载自blog.csdn.net/qq_41520636/article/details/143135625