1. 什么是AQS(AbstractQueuedSynchronizer)?
AQS,全称为AbstractQueuedSynchronizer,是Java并发包中核心的基础框架,用于构建锁和同步器。它是java.util.concurrent.locks
包中的基础组件,为多个同步类提供了共享的功能,比如ReentrantLock
、CountDownLatch
、Semaphore
等。
AQS的主要作用是:
- 管理锁的状态:通过一个整型状态
state
变量来表示锁的状态,如是否获取锁,当前锁的持有线程是谁。 - FIFO等待队列:当线程争抢资源失败时,AQS会将其放入一个FIFO(先进先出)队列中进行管理。
2. AQS的工作原理
AQS通过一个整数变量state
来表示锁的状态:
- 当
state == 0
时,表示锁是空闲状态; - 当
state > 0
时,表示锁已被线程占用。
AQS通过两种模式来实现锁机制:
- 独占模式(Exclusive):只有一个线程能获取锁,如
ReentrantLock
。 - 共享模式(Shared):多个线程可以同时获取锁,如
CountDownLatch
。
3. 关键机制
- 获取锁的流程:尝试获取资源(
tryAcquire
),如果成功则直接执行;否则,进入等待队列。 - 释放锁的流程:通过
tryRelease
释放资源,唤醒队列中的等待线程。 - 等待队列:如果获取锁失败,线程会被放入等待队列,等待其他线程释放锁资源。
4. Java模拟代码:自定义锁基于AQS
为了深入理解AQS的工作原理,以下是一个基于AQS的简易独占锁的实现:
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class CustomLock {
// 内部类继承AQS,实现独占锁逻辑
private static class Sync extends AbstractQueuedSynchronizer {
// 尝试获取锁(独占模式)
@Override
protected boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread()); // 设置持有锁的线程
return true;
}
return false;
}
// 尝试释放锁
@Override
protected boolean tryRelease(int releases) {
if (getState() == 0) {
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null); // 清空持有锁的线程
setState(0); // 设置状态为0,表示锁已释放
return true;
}
// 是否被占用
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();
}
}
public class CustomLockTest {
public static void main(String[] args) {
CustomLock lock = new CustomLock();
// 创建多个线程模拟锁的竞争
for (int i = 0; i < 5; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " is trying to acquire the lock.");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " has acquired the lock.");
Thread.sleep(2000); // 模拟业务处理
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " is releasing the lock.");
lock.unlock();
}
}).start();
}
}
}
5. 代码详细解释
-
Sync
类继承自AbstractQueuedSynchronizer
:tryAcquire
:尝试获取锁,通过CAS操作(compareAndSetState(0, 1)
)来判断当前锁是否空闲。如果成功获取锁,则设置当前线程为锁的持有者。tryRelease
:释放锁时,检查锁状态并将其设置为0
,同时清除持有锁的线程信息。isHeldExclusively
:用于检查当前线程是否持有锁。
-
CustomLock
类:lock()
:调用AQS的acquire
方法尝试获取锁。unlock()
:调用AQS的release
方法释放锁。
-
CustomLockTest
类:- 创建多个线程,模拟锁的竞争。每个线程尝试获取锁,成功后模拟业务处理,并在完成后释放锁。
6. 运行结果
运行后,多个线程尝试获取锁,由于我们实现的是独占锁,只有一个线程可以持有锁,其他线程会进入等待状态,直到锁被释放。
示例输出(简化):
Thread-0 is trying to acquire the lock.
Thread-0 has acquired the lock.
Thread-1 is trying to acquire the lock.
Thread-2 is trying to acquire the lock.
Thread-0 is releasing the lock.
Thread-1 has acquired the lock.
Thread-1 is releasing the lock.
...
从输出中可以看出,多个线程争抢锁,只有一个线程可以获取,其他线程会等待。
7. 使用场景及问题解决
使用场景:
- 锁的实现:AQS是实现Java锁机制的核心,可以用它实现自定义的同步器,如互斥锁、读写锁等。
- 信号量:AQS的共享模式可以用来实现
Semaphore
,允许多个线程同时获取锁。 - 倒计时器(CountDownLatch):通过AQS实现线程同步,多个线程等待一个事件发生。
解决的问题:
- 线程安全的资源访问:通过AQS可以确保在多线程环境下,多个线程对共享资源的有序访问,避免竞争条件和数据不一致问题。
- 灵活的同步控制:通过AQS的独占和共享模式,可以实现灵活的同步控制策略,适应不同的业务需求。
8. 借用AQS思想的业务场景
场景示例:分布式任务调度器的锁控制
在分布式系统中,多个节点可能会同时尝试执行某个任务,比如调度定时任务或处理某些业务逻辑。为了防止多个节点同时执行同一个任务,我们可以借助AQS实现分布式任务调度锁。
使用AQS的独占模式,可以保证只有一个节点能够获取锁,并执行任务。其他节点会被阻塞,直到锁被释放。
public class DistributedTaskScheduler {
private static final CustomLock lock = new CustomLock();
public static void main(String[] args) {
// 多个节点模拟任务调度
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
if (lock.isLocked()) {
System.out.println(Thread.currentThread().getName() + ": Task already in progress by another node.");
} else {
lock.lock();
System.out.println(Thread.currentThread().getName() + ": Acquired lock and started task.");
Thread.sleep(3000); // 模拟任务执行
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + ": Released lock and finished task.");
}
}).start();
}
}
}
解释:
- 在分布式任务调度中,多个节点会尝试获取锁并执行任务,使用AQS确保只有一个节点能成功获取锁,防止任务重复执行。