1. AQS(AbstractQueuedSynchronizer)的同步队列和条件队列原理
同步队列(Sync Queue)
同步队列是 AQS 的核心部分,管理着线程的排队和唤醒。在并发环境中,当一个线程请求获取锁(或者同步状态)失败时,它会被放入同步队列中等待,直到锁可用时重新被唤醒。
核心原理:
- 双向链表结构:AQS 的同步队列是一个双向链表,头节点通常是持有锁的线程,后续节点是等待获取锁的线程。
- 自旋和阻塞机制:当某个线程获取锁失败时,它会自旋一段时间(忙等待),如果锁仍然不可用,则会被挂起并放入队列中。线程被唤醒时重新尝试获取锁。
- 锁的释放和唤醒机制:当持有锁的线程释放锁时,AQS 会唤醒队列中的下一个等待线程,让它尝试获取锁。
条件队列(Condition Queue)
条件队列用于实现 Condition
的等待和通知机制。在 Java 的并发库中,Condition
对象可以用来管理线程的等待条件,当线程等待某个条件时,它会被放入条件队列中,直到被其他线程唤醒。
核心原理:
- 单向链表结构:条件队列是一个单向链表结构,存储的是等待特定条件的线程。
- 等待和唤醒机制:线程调用
await()
方法时,会进入条件队列,释放当前持有的锁并进入阻塞状态。其他线程调用signal()
或signalAll()
方法时,会将条件队列中的线程移动到同步队列中,重新尝试获取锁。
两者的关系:
- 同步队列是管理线程获取同步资源的主要队列,而条件队列则用于管理线程的等待条件。
- 当条件满足时,线程会从条件队列转移到同步队列中,等待锁的获取。
2. AQS 的独占模式和共享模式
AQS 支持两种锁的获取模式:独占模式 和 共享模式。
独占模式(Exclusive Mode)
在独占模式下,只有一个线程能获取同步状态(例如锁),其他线程必须等待。当当前线程释放同步状态后,其他线程才能尝试获取。
-
典型例子:
ReentrantLock
的独占锁。 -
工作原理:
- 当线程获取同步状态(如锁)时,如果成功则进入临界区。
- 如果失败,线程进入同步队列等待。
- 当持有锁的线程释放锁时,队列中的下一个线程被唤醒并尝试获取锁。
-
适用场景:独占模式适用于所有只能有一个线程访问临界区的场景,比如互斥锁。
共享模式(Shared Mode)
在共享模式下,多个线程可以同时获取同步状态(如共享资源),这意味着允许多个线程并发执行。
-
典型例子:
CountDownLatch
和Semaphore
的共享锁。 -
工作原理:
- 当多个线程试图获取同步状态时,如果资源充足,多个线程可以同时获得访问权限。
- 如果资源不足,部分线程进入同步队列等待。
- 当资源释放时,队列中的等待线程可以被唤醒并继续尝试获取资源。
-
适用场景:共享模式适用于需要允许多个线程同时访问共享资源的场景,例如限流器、计数器等。
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();
}
}
代码解释:
Sync
类:基于 AQS 实现,提供了tryAcquire
和tryRelease
方法,控制锁的获取和释放。- 独占模式:在
tryAcquire
中,只有当前线程可以获取锁,其他线程必须等待。 - 双向同步队列:如果线程获取锁失败,会被加入同步队列,等待唤醒。
- 条件队列:
newCondition
方法创建条件队列,用于实现等待通知机制。
运行结果:
在这个简易的 ReentrantLock
实现中,当 Thread t1
持有锁时,Thread t2
会进入同步队列等待,直到 Thread t1
释放锁后才会被唤醒。
4. AQS 的使用场景与问题解决
使用场景:
- 互斥锁:如
ReentrantLock
,用于确保同一时刻只有一个线程进入临界区。 - 共享锁:如
Semaphore
,允许多个线程同时访问共享资源。 - 同步器:如
CountDownLatch
,用于协调多个线程之间的同步。 - 条件等待:如
Condition
,用于实现复杂的线程间通信和条件等待。
业务场景:
可以在需要高效管理并发任务和线程控制的场景下使用 AQS,如任务调度器、流量限流、资源访问控制等。