北京JAVA基础面试30天打卡03
1.AQS(AbstractQueuedSynchronizer)
AQS(AbstractQueuedSynchronizer) 是 Java 并发包(java.util.concurrent)中的一个核心框架类,用于构建锁和同步器(如 ReentrantLock、Semaphore、CountDownLatch 等)。它提供了一种基于队列的同步机制,管理线程的阻塞和唤醒。
核心思想
AQS 通过一个 FIFO 队列(基于 CLH 锁队列的变种)和一个 状态变量(int state)来实现线程同步:
- 状态变量:表示锁或同步器的状态(如锁是否被占用,计数器的值等)。
- 队列:维护等待获取锁的线程,线程进入队列后会被阻塞,直到满足条件被唤醒。
- CAS 操作:通过 Compare-And-Swap(如 compareAndSetState)实现状态的原子更新。
工作原理
-
状态管理:AQS 维护一个 volatile int state,通过 CAS 操作修改状态。例如,ReentrantLock 中 state=0 表示锁空闲,state>0 表示锁被占用。
-
队列管理:当线程无法获取锁时,加入 AQS 的等待队列(CLH 队列),线程会被挂起(LockSupport.park)。
-
获取/释放
- 获取锁:子类实现 tryAcquire 方法,尝试更新 state。成功则获取锁,失败则进入队列。
- 释放锁:子类实现 tryRelease 方法,更新 state,并唤醒队列中的下一个线程(LockSupport.unpark)。
-
条件队列:AQS 支持 Condition 接口(如 ConditionObject),用于实现线程的条件等待(如 await 和 signal)。
核心方法
-
模板方法
(由 AQS 提供,子类实现):
- tryAcquire(int):尝试获取锁。
- tryRelease(int):尝试释放锁。
- tryAcquireShared(int):尝试共享式获取资源。
- tryReleaseShared(int):尝试共享式释放资源。
- isHeldExclusively():判断锁是否独占。
-
核心 API
- acquire(int):获取锁(独占模式),包括尝试获取和入队阻塞。
- release(int):释放锁,唤醒队列中的线程。
- acquireShared(int) / releaseShared(int):共享模式的获取和释放。
使用场景
AQS 是许多 JUC 工具类的底层实现:
- ReentrantLock:基于 AQS 实现可重入锁,state 表示锁的重入次数。
- ReentrantReadWriteLock:读写锁,state 高 16 位表示读锁计数,低 16 位表示写锁状态。
- Semaphore:信号量,state 表示可用许可数。
- CountDownLatch:倒计数器,state 表示剩余计数。
- CyclicBarrier:state 用于管理屏障状态。
优点
- 灵活性:通过模板方法,子类可以自定义锁的语义。
- 高效性:基于 CAS 和队列,减少上下文切换。
- 可扩展性:支持独占锁、共享锁、条件等待等。
2. Synchronized 与 ReentrantLock 的区别
Synchronized 和 ReentrantLock 都是 Java 中用于实现线程同步的机制,但它们在功能、性能和使用方式上有显著区别。
Synchronized
-
定义:Java 关键字,内置的锁机制,由 JVM 管理。
-
特点
- 使用简单:通过 synchronized 关键字修饰代码块或方法,自动获取和释放锁。
- 独占锁:同一时间只有一个线程可以持有锁。
- 可重入:同一线程可以多次获取同一锁(锁计数器递增)。
- 不可中断:线程在等待锁时无法被中断。
- 自动释放:锁在代码块结束或异常时自动释放。
- 无条件等待:不支持 Condition 机制,无法实现复杂等待/通知逻辑。
- 性能:在 Java 6 之后,JVM 对 synchronized 进行了优化(如锁消除、锁粗化、偏向锁、轻量级锁),性能接近 ReentrantLock。
ReentrantLock
-
定义:基于 AQS 实现的显式锁,位于 java.util.concurrent.locks 包。
-
特点
- 显式锁:需要手动调用 lock() 和 unlock() 获取/释放锁。
- 可重入:与 synchronized 类似,支持重入。
- 可中断:支持 lockInterruptibly(),允许线程在等待锁时被中断。
- 公平/非公平:支持公平锁(按等待顺序分配)和非公平锁(默认,允许插队)。
- 条件等待:支持 Condition 对象,可实现多个等待队列(如生产者-消费者模型)。
- 灵活性:支持超时获取锁(tryLock(timeout))。
- 手动释放:需在 finally 块中调用 unlock(),否则可能导致死锁。
区别总结
特性 | Synchronized | ReentrantLock |
---|---|---|
实现方式 | JVM 内置,关键字 | JUC 包,基于 AQS |
使用方式 | 自动获取/释放锁 | 手动 lock() / unlock() |
可中断性 | 不可中断 | 可中断(lockInterruptibly) |
公平性 | 非公平锁 | 可选公平锁或非公平锁 |
条件等待 | 无 Condition 支持 | 支持 Condition(多等待队列) |
灵活性 | 简单但功能有限 | 功能丰富(超时、尝试锁等) |
性能 | Java 6 后优化,接近 ReentrantLock | 稍高(尤其在复杂场景下) |
异常处理 | 自动释放锁 | 需手动释放(finally 块) |
使用场景
- Synchronized:适合简单同步场景,如方法级或代码块级锁,代码简洁,适合快速开发。
- ReentrantLock:适合需要高级功能的场景,如公平锁、可中断锁、条件等待、超时获取锁等。
代码示例:
java
// Synchronizedsynchronized (obj) {// 同步代码块
}// ReentrantLock
ReentrantLock lock = new ReentrantLock();try {lock.lock();// 同步代码块
} finally {lock.unlock();
}
3. Java 中 volatile 关键字的作用
volatile 是 Java 中的一个关键字,用于修饰变量,确保变量在多线程环境下的可见性和有序性,但不保证原子性。
作用
-
保证可见性
- 当一个线程修改了 volatile 变量的值,新值对其他线程立即可见。
- 这是因为 volatile 变量的读写操作会直接与主内存交互,绕过线程的本地缓存(工作内存)。
- 解决了多线程环境下缓存不一致的问题。
-
防止指令重排序
- volatile 变量的读写操作会添加内存屏障(Memory Barrier),防止 JVM 或 CPU 进行指令重排序。
- 确保代码的执行顺序与程序顺序一致(如初始化对象的正确性)。
-
不保证原子性
- volatile 仅保证读写的原子性,但对复合操作(如 i++)不保证原子性。
- 例如,volatile int i 的 i++ 操作分为读、改、写三步,多个线程可能交错执行,导致数据不一致。
使用场景
- 状态标志:如 volatile boolean running = true,用于控制线程的启动/停止。
- 单次写入,多次读取:如配置变量,初始化后只读不写。
- 配合其他机制:与 synchronized 或 Atomic 类结合使用,解决原子性问题。
示例
java
class VolatileExample {volatile boolean flag = false;void writer() {flag = true; // 写入对其他线程立即可见}void reader() {if (flag) { // 读取到最新值System.out.println("Flag is true");}}
}
volatile 与 synchronized 的区别
- volatile
- 仅保证变量的可见性和有序性,不保证复合操作的原子性。
- 轻量级,适用于简单场景(如标志位)。
- synchronized
- 保证原子性、可见性和有序性。
- 重量级,适用于需要锁保护的复杂同步场景。
注意事项
- volatile 不适合需要原子性的场景(如计数器),应使用 AtomicInteger 或 synchronized。
- volatile 的性能开销低于 synchronized,但仍需谨慎使用,避免滥用。
总结
- AQS:Java 并发框架的核心,基于状态变量和队列实现锁和同步器,广泛用于 JUC 工具类。
- Synchronized vs ReentrantLock:Synchronized 简单但功能有限,ReentrantLock 提供更多灵活性(如公平锁、条件等待)。
- volatile:确保变量的可见性和有序性,适合状态标志等场景,但不保证原子性。
- 明天二次补充拓展,希望大家坚持下去,不见彩虹也有阳光~