1. 写在前面
AQS(AbstractQueuedSynchronizer)是Java并发包(java.util.concurrent)中的一个抽象类,用于实现同步器(如锁、信号量、栅栏等)。AQS提供了一种基于FIFO队列的机制来管理线程的竞争和等待状态。其主要作用是简化同步器的实现,通过提供通用的同步状态管理和线程排队机制,使得开发者可以专注于特定同步器的逻辑。了解AQS的工作原理和应用场景是高级Java开发者需要掌握的重要技能。以下是一些常见的AQS面试题及其详细解答。
2. AQS的工作原理是什么?
AQS的核心工作原理基于一个FIFO等待队列和一个同步状态(state)。其主要步骤如下:
- 同步状态:AQS通过一个 int 类型的变量 state 来表示同步状态。子类通过重写 tryAcquire、tryRelease 等方法来定义获取和释放同步状态的逻辑。
- 等待队列:当线程无法获取同步状态时,会被加入到AQS的FIFO等待队列中,队列中的每个节点(Node)表示一个等待的线程。
- 独占模式和共享模式:AQS支持独占模式(如独占锁)和共享模式(如共享锁、信号量)。在独占模式下,只有一个线程可以获取同步状态;在共享模式下,多个线程可以同时获取同步状态。
- 模板方法:AQS通过模板方法模式提供了通用的同步机制,子类只需实现特定的同步逻辑。
3. AQS中的Node节点是什么?其作用是什么?
AQS中的Node节点是一个内部类 AbstractQueuedSynchronizer.Node,用于表示等待队列中的每个线程。Node节点包含以下重要信息:
- 线程引用:表示当前节点所关联的线程。
- 等待状态:表示当前节点的等待状态,如 SIGNAL(等待唤醒)、CANCELLED(取消)等。
- 前驱和后继节点:用于在等待队列中形成双向链表。
- 模式:表示当前节点是独占模式还是共享模式。
Node节点的作用是管理等待队列中的线程状态和排队顺序,确保线程能够按照FIFO顺序被唤醒和执行。
4. AQS中的独占模式和共享模式有什么区别?
AQS支持两种模式:独占模式和共享模式。
4.1 独占模式
- 只有一个线程可以获取同步状态
- 典型应用:独占锁(如 ReentrantLock)
- 主要方法:tryAcquire、tryRelease
4.2 共享模式
- 多个线程可以同时获取同步状态
- 典型应用:共享锁(如 ReadWriteLock 中的读锁)、信号量(如 Semaphore)
- 主要方法:tryAcquireShared、tryReleaseShared
5. 如何使用AQS实现一个简单的独占锁?
可以通过继承AQS并重写其方法来实现一个简单的独占锁。以下是一个示例:
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class SimpleLock {
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(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();
}
}
6. 如何使用AQS实现一个简单的共享锁?
可以通过继承AQS并重写其方法来实现一个简单的共享锁。以下是一个示例:
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class SimpleSharedLock {
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected int tryAcquireShared(int arg) {
for (;;) {
int current = getState();
int newCount = current + arg;
if (compareAndSetState(current, newCount)) {
return newCount;
}
}
}
@Override
protected boolean tryReleaseShared(int arg) {
for (;;) {
int current = getState();
int newCount = current - arg;
if (compareAndSetState(current, newCount)) {
return newCount == 0;
}
}
}
}
private final Sync sync = new Sync();
public void lock() {
sync.acquireShared(1);
}
public void unlock() {
sync.releaseShared(1);
}
}
7. AQS的公平锁和非公平锁有什么区别?
AQS支持公平锁和非公平锁两种模式:
7.1 公平锁
- 线程按照FIFO顺序获取锁,先到先得。
- 公平锁避免了线程饥饿,但可能会导致较高的上下文切换开销。
- 典型实现:ReentrantLock 的公平模式。
7.2 非公平锁
- 线程可以插队获取锁,不保证FIFO顺序。
- 非公平锁可能会导致线程饥饿,但通常性能较高,因为减少了上下文切换。
- 典型实现:ReentrantLock 的非公平模式(默认)。
8. AQS的Condition机制是如何实现的?
AQS的Condition机制通过内部类 ConditionObject 实现。ConditionObject 提供了 await 和 signal 等方法,用于线程的等待和唤醒。其工作原理如下:
- 等待队列:每个Condition对象都有一个单独的等待队列,线程调用 await 方法时,会被加入到该等待队列中,并释放当前持有的锁。
- 唤醒机制:线程调用 signal 方法时,会从等待队列中唤醒一个线程,并将其移到同步队列中,等待获取锁。
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
public class SimpleLockWithCondition {
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
protected boolean isHeldExclusively() {
return getState() == 1;
}
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();
}
}