一、阻塞队列核心概念
1. 阻塞队列的定义与特性
java
// BlockingQueue 接口定义的核心方法
public interface BlockingQueue<E> extends Queue<E> {
/*
阻塞队列的四大核心操作:
1. 插入操作(可能阻塞)
- put(E e): 队列满时阻塞,直到有空间
- offer(E e, long timeout, TimeUnit unit): 限时等待
2. 移除操作(可能阻塞)
- take(): 队列空时阻塞,直到有元素
- poll(long timeout, TimeUnit unit): 限时等待
3. 检查操作(非阻塞)
- peek(): 查看队首元素,不删除
- size(): 返回队列当前元素数
4. 特殊操作
- drainTo(Collection<? super E> c): 批量转移
- remainingCapacity(): 剩余容量
*/
}
2. Java内置阻塞队列对比
text
Java并发包提供的阻塞队列实现:
1. ArrayBlockingQueue(有界数组队列)
- 基于数组的FIFO队列
- 固定容量,可选公平/非公平锁
2. LinkedBlockingQueue(可选有界链表队列)
- 基于链表的FIFO队列
- 默认无界(Integer.MAX_VALUE),可指定容量
3. PriorityBlockingQueue(优先级队列)
- 无界队列,元素按优先级排序
- 基于堆实现,非FIFO
4. DelayQueue(延迟队列)
- 无界队列,元素延迟到期
- 基于PriorityQueue实现
5. SynchronousQueue(同步队列)
- 不存储元素的阻塞队列
- 每个插入操作必须等待移除操作
6. LinkedTransferQueue(转移队列)
- 无界队列,支持"匹配"语义
- 结合了SynchronousQueue和LinkedBlockingQueue特性
二、核心实现机制
1. 阻塞队列的底层依赖
java
// 所有阻塞队列实现的核心机制
public class BlockingMechanism {
/*
三大核心机制:
1. 锁机制(Lock)
- ReentrantLock:保证线程安全
- 条件变量(Condition):实现阻塞/唤醒
2. 等待/通知模式
- notEmpty:队列空时等待,非空时唤醒
- notFull:队列满时等待,不满时唤醒
3. 数据结构
- 数组:ArrayBlockingQueue
- 链表:LinkedBlockingQueue
- 堆:PriorityBlockingQueue
*/
}
三、ArrayBlockingQueue 源码解析
1. 内部数据结构
java
// ArrayBlockingQueue 的核心字段
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
// 1. 核心数据结构:循环数组
final Object[] items;
// 2. 指针:队首和队尾
int takeIndex; // 下一个要取元素的位置
int putIndex; // 下一个要放元素的位置
int count; // 队列中元素数量
// 3. 锁与条件变量
final ReentrantLock lock;
private final Condition notEmpty; // 队列非空条件
private final Condition notFull; // 队列非满条件
// 4. 可选公平性策略
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair); // 公平/非公平锁
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
2. put() 方法实现(阻塞插入)
java
// 核心阻塞插入方法
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e); // 检查空值
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 可中断加锁
try {
// 如果队列满,在notFull条件上等待
while (count == items.length) {
notFull.await(); // 释放锁并等待,被唤醒后重新获取锁
}
enqueue(e); // 队列不满,执行入队
} finally {
lock.unlock(); // 确保锁释放
}
}
// 入队操作(私有方法)
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x; // 放入元素
// 循环数组:到达末尾后回到开头
if (++putIndex == items.length)
putIndex = 0;
count++; // 元素计数增加
// 唤醒在notEmpty上等待的消费者线程
notEmpty.signal();
}
3. take() 方法实现(阻塞移除)
java
// 核心阻塞移除方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 可中断加锁
try {
// 如果队列空,在notEmpty条件上等待
while (count == 0) {
notEmpty.await(); // 释放锁并等待
}
return dequeue(); // 队列不空,执行出队
} finally {
lock.unlock();
}
}
// 出队操作(私有方法)
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex]; // 取出元素
items[takeIndex] = null; // 置空,帮助GC
// 循环数组:到达末尾后回到开头
if (++takeIndex == items.length)
takeIndex = 0;
count--; // 元素计数减少
// 如果使用了迭代器,可能需要维护迭代器状态
if (itrs != null)
itrs.elementDequeued();
// 唤醒在notFull上等待的生产者线程
notFull.signal();
return x;
}
4. 循环数组工作原理
java
// 循环数组的索引计算
public class CircularArray {
/*
初始状态:
items = [null, null, null, null, null] // 容量5
takeIndex = 0, putIndex = 0, count = 0
操作序列:
1. put(A) → items[0] = A, putIndex=1, count=1
数组:[A, null, null, null, null]
2. put(B) → items[1] = B, putIndex=2, count=2
数组:[A, B, null, null, null]
3. take() → 取走A, takeIndex=1, count=1
数组:[null, B, null, null, null]
4. put(C) → items[2] = C, putIndex=3, count=2
数组:[null, B, C, null, null]
5. 一直put到满,再take到空...
循环机制:
putIndex到达末尾后:if (++putIndex == items.length) putIndex = 0;
takeIndex同理
判断满:count == items.length
判断空:count == 0
*/
}
5. 公平性策略影响
java
// 公平锁 vs 非公平锁对性能的影响
public class FairnessComparison {
/*
公平锁(fair = true):
- 线程按请求锁的顺序获得锁
- 避免线程饥饿
- 性能较低(需要维护队列)
非公平锁(fair = false,默认):
- 线程竞争获取锁,可能插队
- 可能造成某些线程饥饿
- 性能较高(吞吐量大)
生产环境建议:
- 高并发场景:非公平锁(性能优先)
- 对响应时间敏感:公平锁(公平性优先)
- 默认使用非公平锁,除非有特殊需求
*/
}
四、LinkedBlockingQueue 源码解析
1. 内部数据结构
java
// LinkedBlockingQueue 节点定义
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
// 1. 节点类(静态内部类)
static class Node<E> {
E item; // 存储的数据
Node<E> next; // 下一个节点引用
Node(E x) { item = x; }
}
// 2. 容量控制
private final int capacity; // 队列容量(默认Integer.MAX_VALUE)
private final AtomicInteger count = new AtomicInteger(); // 当前元素数
// 3. 双锁设计(提升并发性能)
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
// 4. 头尾指针
transient Node<E> head; // 头节点(总是哑节点)
private transient Node<E> last; // 尾节点
// 构造方法
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE); // 默认无界
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null); // 初始化哑节点
}
}
2. 双锁分离设计
java
// LinkedBlockingQueue 的并发优化:双锁分离
public class TwoLockDesign {
/*
为什么使用双锁?
1. 传统单锁问题:
- put和take操作都需要获取同一把锁
- 生产者和消费者互相阻塞
- 并发性能受限
2. 双锁分离优势:
- 读锁(takeLock):控制出队操作
- 写锁(putLock):控制入队操作
- 生产者和消费者可以并行操作(除非队列空/满)
3. 特殊情况处理:
- 队列从空变为非空:需要获取读锁唤醒消费者
- 队列从满变为不满:需要获取写锁唤醒生产者
性能对比(4核8线程):
- ArrayBlockingQueue:约 80万 ops/sec
- LinkedBlockingQueue:约 150万 ops/sec
*/
}
3. put() 方法实现
java
// LinkedBlockingQueue 的 put 方法
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1; // 用于记录操作前的元素数
Node<E> node = new Node<E>(e); // 创建新节点
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly(); // 获取写锁
try {
// 如果队列满,等待
while (count.get() == capacity) {
notFull.await();
}
// 入队操作
enqueue(node);
// 原子增加计数,并获取旧值
c = count.getAndIncrement();
// 如果插入后队列未满,唤醒其他生产者
if (c + 1 < capacity) {
notFull.signal();
}
} finally {
putLock.unlock();
}
// 如果插入前队列为空,唤醒消费者
if (c == 0) {
signalNotEmpty();
}
}
// 入队操作
private void enqueue(Node<E> node) {
// 将新节点链接到尾部
last = last.next = node;
}
// 唤醒等待的消费者
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock(); // 必须获取读锁才能发信号
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
4. take() 方法实现
java
// LinkedBlockingQueue 的 take 方法
public E take() throws InterruptedException {
E x;
int c = -1; // 用于记录操作前的元素数
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly(); // 获取读锁
try {
// 如果队列空,等待
while (count.get() == 0) {
notEmpty.await();
}
// 出队操作
x = dequeue();
// 原子减少计数,并获取旧值
c = count.getAndDecrement();
// 如果取出后队列不空,唤醒其他消费者
if (c > 1) {
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
// 如果取出前队列为满,唤醒生产者
if (c == capacity) {
signalNotFull();
}
return x;
}
// 出队操作
private E dequeue() {
Node<E> h = head; // 当前头节点(哑节点)
Node<E> first = h.next; // 第一个实际数据节点
// 断开哑节点
h.next = h; // help GC(将哑节点指向自己)
// 更新头节点
head = first;
E x = first.item;
first.item = null; // 帮助GC
return x;
}
// 唤醒等待的生产者
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock(); // 必须获取写锁才能发信号
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
五、其他阻塞队列实现原理
1. PriorityBlockingQueue(优先级队列)
java
// 基于堆的优先级队列实现
public class PriorityBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
// 1. 核心数据结构:二叉堆
private transient Object[] queue; // 堆数组
private transient int size; // 元素数量
private final Comparator<? super E> comparator; // 比较器
// 2. 锁机制(只有一个锁,因为需要维护堆结构)
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
// 3. 扩容机制(无界队列)
private static final int DEFAULT_INITIAL_CAPACITY = 11;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 出队(总是返回优先级最高的元素)
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
E result;
try {
// 如果队列空,等待
while ((result = dequeue()) == null) {
notEmpty.await();
}
} finally {
lock.unlock();
}
return result;
}
// 堆的下沉操作(维护堆性质)
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // 最后一个非叶子节点
while (k < half) {
int child = (k << 1) + 1; // 左孩子索引
Object c = queue[child]; // 左孩子
int right = child + 1; // 右孩子索引
// 选择较小的孩子
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0) {
c = queue[child = right];
}
// 如果x小于等于孩子,堆性质满足
if (key.compareTo((E) c) <= 0) {
break;
}
// 否则,孩子上移
queue[k] = c;
k = child;
}
queue[k] = key;
}
}
2. SynchronousQueue(同步队列)
java
// 不存储元素的阻塞队列
public class SynchronousQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/*
核心特性:
1. 不存储元素,每个put必须等待一个take
2. 可以看作容量为1的队列,但连这个"1"都不存储
3. 支持公平模式(队列)和非公平模式(栈)
实现原理:
1. 双数据结构模式:
- TransferStack(非公平,LIFO)
- TransferQueue(公平,FIFO)
2. 核心Transfer接口:
- transfer(E e, boolean timed, long nanos)
- 如果当前有等待的消费者,直接传递
- 否则,生产者等待
适用场景:
- 线程间直接传递数据
- Executors.newCachedThreadPool() 使用
*/
// 核心:TransferStack节点
static final class SNode {
volatile SNode next; // 下一个节点
volatile SNode match; // 匹配的节点
volatile Thread waiter; // 等待的线程
Object item; // 数据项
int mode; // 模式:REQUEST(消费者)或DATA(生产者)
}
// 核心transfer方法(简化版)
E transfer(E e, boolean timed, long nanos) {
SNode s = null;
int mode = (e == null) ? REQUEST : DATA; // 判断是生产者还是消费者
for (;;) { // 自旋
SNode h = head;
if (h == null || h.mode == mode) { // 栈空或相同模式
// 等待匹配
if (timed && nanos <= 0) { // 超时
if (s != null && s.cancel())
clean(s);
return null;
}
// 创建节点并入栈
s = new SNode(e);
if (!casHead(h, s)) // CAS更新栈顶
continue;
// 等待匹配
SNode m = awaitFulfill(s, timed, nanos);
if (m == s) { // 等待被取消
clean(s);
return null;
}
// 匹配成功,返回数据
return (E) ((mode == REQUEST) ? m.item : s.item);
} else { // 不同模式,可以匹配
SNode m = h.next;
if (m == null) // 没有等待者
continue;
// 尝试匹配
if (casHead(h, m.next)) { // 弹出匹配的节点
// 唤醒等待的线程
LockSupport.unpark(m.waiter);
return (E) ((mode == REQUEST) ? m.item : s.item);
}
}
}
}
}
六、阻塞队列的性能优化技巧
1. 减少锁竞争的策略
java
// 1. 批量操作减少锁获取次数
public class BatchOperations {
// 批量插入
public void batchPut(List<E> items) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
for (E item : items) {
while (count == items.length) {
notFull.await();
}
enqueue(item);
}
} finally {
lock.unlock();
}
}
// 批量消费
public List<E> batchTake(int maxElements) {
List<E> result = new ArrayList<>(maxElements);
final ReentrantLock lock = this.lock;
lock.lock();
try {
for (int i = 0; i < maxElements; i++) {
while (count == 0) {
notEmpty.await();
}
result.add(dequeue());
}
} finally {
lock.unlock();
}
return result;
}
}
// 2. 使用drainTo方法
public int drainTo(Collection<? super E> c, int maxElements) {
// 一次性转移多个元素,减少锁开销
// 实现中通常只获取一次锁
}
2. 避免虚假唤醒
java
// 条件等待的正确模式
public class CorrectAwaitPattern {
/*
虚假唤醒(Spurious Wakeup):
- 线程可能在没有被signal的情况下从await()返回
- POSIX标准允许,JVM也可能发生
正确做法:总是使用while循环检查条件
*/
// ❌ 错误做法:if判断
public void wrongPut(E e) throws InterruptedException {
lock.lock();
try {
if (queue.isFull()) { // if判断可能被虚假唤醒
notFull.await();
}
enqueue(e);
} finally {
lock.unlock();
}
}
// ✅ 正确做法:while循环
public void correctPut(E e) throws InterruptedException {
lock.lock();
try {
while (queue.isFull()) { // while循环抵御虚假唤醒
notFull.await();
}
enqueue(e);
} finally {
lock.unlock();
}
}
}
3. 性能对比与选择建议
markdown
| 队列类型 | 优点 | 缺点 | 适用场景 |
|---------|------|------|---------|
| **ArrayBlockingQueue** | 内存连续,缓存友好 | 容量固定,扩容麻烦 | 固定容量,性能要求高 |
| **LinkedBlockingQueue** | 动态扩容,吞吐量高 | 内存不连续,节点开销 | 无界或大容量队列 |
| **PriorityBlockingQueue** | 优先级处理 | 排序开销,非FIFO | 任务调度,优先级处理 |
| **SynchronousQueue** | 直接传递,零存储 | 必须有配对线程 | 线程池工作队列 |
| **DelayQueue** | 延迟执行 | 排序开销 | 定时任务,缓存过期 |
选择建议:
1. 固定容量高吞吐 → ArrayBlockingQueue
2. 无界/大容量 → LinkedBlockingQueue
3. 优先级调度 → PriorityBlockingQueue
4. 线程间直接传递 → SynchronousQueue
5. 延迟任务 → DelayQueue
七、阻塞队列在生产环境的实践
1. 线程池的工作队列
java
复制
下载
// ThreadPoolExecutor 如何使用阻塞队列
public class ThreadPoolExecutorExample {
// 1. FixedThreadPool 使用 LinkedBlockingQueue(无界)
ExecutorService fixedPool = Executors.newFixedThreadPool(10);
// 内部实现:new ThreadPoolExecutor(nThreads, nThreads,
// 0L, TimeUnit.MILLISECONDS,
// new LinkedBlockingQueue<Runnable>());
// 2. CachedThreadPool 使用 SynchronousQueue
ExecutorService cachedPool = Executors.newCachedThreadPool();
// 内部实现:new ThreadPoolExecutor(0, Integer.MAX_VALUE,
// 60L, TimeUnit.SECONDS,
// new SynchronousQueue<Runnable>());
// 3. SingleThreadExecutor 使用 LinkedBlockingQueue
ExecutorService singlePool = Executors.newSingleThreadExecutor();
// 4. ScheduledThreadPool 使用 DelayedWorkQueue
ScheduledExecutorService scheduledPool =
Executors.newScheduledThreadPool(5);
}
2. 生产者-消费者模式实现
java
// 使用阻塞队列实现生产者-消费者
public class ProducerConsumerExample {
private final BlockingQueue<Task> queue = new LinkedBlockingQueue<>(100);
// 生产者
class Producer implements Runnable {
private final Random random = new Random();
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 生产任务
Task task = generateTask();
// 放入队列(队列满时阻塞)
queue.put(task);
System.out.println("生产任务: " + task);
// 控制生产速度
Thread.sleep(random.nextInt(1000));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 消费者
class Consumer implements Runnable {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 从队列取任务(队列空时阻塞)
Task task = queue.take();
System.out.println("消费任务: " + task);
// 处理任务
processTask(task);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 启动生产者和消费者
public void start() {
ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(new Producer());
executor.execute(new Producer());
executor.execute(new Consumer());
executor.execute(new Consumer());
}
}
3. 流量控制与限流
java
// 使用阻塞队列实现简单的限流
public class RateLimiterWithBlockingQueue {
private final BlockingQueue<Object> tokenBucket;
public RateLimiterWithBlockingQueue(int permitsPerSecond) {
// 创建指定容量的队列
tokenBucket = new ArrayBlockingQueue<>(permitsPerSecond);
// 初始化令牌
for (int i = 0; i < permitsPerSecond; i++) {
tokenBucket.offer(new Object());
}
// 定时补充令牌
ScheduledExecutorService scheduler =
Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> {
try {
tokenBucket.put(new Object());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, 0, 1_000_000_000L / permitsPerSecond, TimeUnit.NANOSECONDS);
}
// 获取令牌(阻塞直到获取)
public void acquire() throws InterruptedException {
tokenBucket.take(); // 队列空时阻塞
}
// 尝试获取令牌(非阻塞)
public boolean tryAcquire() {
return tokenBucket.poll() != null;
}
// 限时获取令牌
public boolean tryAcquire(long timeout, TimeUnit unit)
throws InterruptedException {
return tokenBucket.poll(timeout, unit) != null;
}
}
八、常见问题与解决方案
1. 死锁问题
java
// 错误的使用方式可能导致死锁
public class DeadlockExample {
private final BlockingQueue<String> queue1 = new ArrayBlockingQueue<>(10);
private final BlockingQueue<String> queue2 = new ArrayBlockingQueue<>(10);
// 线程1:先取queue1,再取queue2
Thread thread1 = new Thread(() -> {
try {
String item1 = queue1.take(); // 可能阻塞
String item2 = queue2.take(); // 可能永远阻塞
System.out.println(item1 + item2);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 线程2:先取queue2,再取queue1
Thread thread2 = new Thread(() -> {
try {
String item2 = queue2.take(); // 可能阻塞
String item1 = queue1.take(); // 可能永远阻塞
System.out.println(item1 + item2);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 解决方案:统一获取顺序,或使用超时
public void solution() {
// 方案1:统一获取顺序
// 方案2:使用poll(timeout)而不是take()
// 方案3:使用tryTransfer模式
}
}
2. 内存泄漏问题
java
// LinkedBlockingQueue 的迭代器可能造成内存泄漏
public class MemoryLeakExample {
/*
问题:LinkedBlockingQueue的迭代器会保留引用
当元素被移除后,迭代器仍可能引用该元素
解决方案:
1. 及时关闭迭代器
2. 使用弱引用
3. 定期清理
*/
public void safeIteration() {
LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 错误的迭代方式
Iterator<String> iterator = queue.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
// 处理item...
}
// 迭代器仍然持有queue的引用
// 正确的做法:限制迭代器作用域
try {
Iterator<String> safeIterator = queue.iterator();
while (safeIterator.hasNext()) {
String item = safeIterator.next();
// 处理item...
}
} finally {
// 迭代器超出作用域,可被GC
}
}
}
3. 性能调优参数
bash
# JVM参数调优建议
# 1. 减少上下文切换
-XX:+UseSpinning # 启用自旋锁
-XX:PreBlockSpin=10 # 自旋次数
# 2. 内存优化
-XX:+UseCompressedOops # 压缩指针
-XX:+UseG1GC # G1垃圾收集器
-XX:MaxGCPauseMillis=200 # 最大GC停顿
# 3. 锁优化
-XX:+UseBiasedLocking # 偏向锁
-XX:BiasedLockingStartupDelay=0 # 立即启用偏向锁
# 4. 线程池优化(配合使用)
-XX:ActiveProcessorCount=4 # 指定CPU核心数
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
九、面试考点总结
1. 高频面试问题
java
public class InterviewQuestions {
/*
1. **基本原理**:
- 阻塞队列是如何实现阻塞的?
- ArrayBlockingQueue和LinkedBlockingQueue的区别?
- 为什么LinkedBlockingQueue使用双锁?
2. **源码实现**:
- put()和take()方法的具体实现?
- 条件变量Condition是如何使用的?
- 虚假唤醒是什么?如何避免?
3. **性能优化**:
- 如何选择不同的阻塞队列实现?
- 阻塞队列的性能瓶颈在哪里?
- 如何避免死锁和内存泄漏?
4. **应用场景**:
- 线程池是如何使用阻塞队列的?
- 如何用阻塞队列实现生产者-消费者模式?
- 如何用阻塞队列实现限流?
5. **并发控制**:
- 公平锁和非公平锁的区别?
- 双锁分离如何提升性能?
- CAS在阻塞队列中的应用?
*/
}
2. 手写简单阻塞队列
java
// 面试常考:手写一个简单的阻塞队列
public class SimpleBlockingQueue<E> {
private final Object[] items;
private int putIndex, takeIndex, count;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
public SimpleBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.items = new Object[capacity];
}
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e);
lock.lockInterruptibly();
try {
while (count == items.length) {
notFull.await();
}
items[putIndex] = e;
if (++putIndex == items.length) putIndex = 0;
count++;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
lock.lockInterruptibly();
try {
while (count == 0) {
notEmpty.await();
}
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length) takeIndex = 0;
count--;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
public int size() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
总结 :阻塞队列是Java并发编程的核心组件,理解其实现原理对于编写高性能并发程序至关重要。关键要掌握锁机制、条件变量、线程安全等核心概念,并能根据实际场景选择合适的阻塞队列实现。