文章目录
前言
前几天看见一个高级Java面试题,我觉得很有代表意义。既考察了面试者的基本锁机制运用,也了解了阻塞队列的产生实现原理。先分享出来,以供鉴赏。
面试题:实现一个自定义的阻塞队列,它应该支持以下功能:
添加元素到队列末尾(如果队列已满,则阻塞添加线程直到有空间)
从队列头部移除元素(如果队列为空,则阻塞获取元素的线程直到有元素可消费)
查询队列是否为空
查询队列是否已满
要求使用Java的ReentrantLock和Condition类来实现线程间的同步。
实战演示
既然题目说到了使用Java的ReentrantLock和Condition类来实现线程间的同步,也就为我们指明了方向,直接可以按照题目的思路进行编码。当然,如果题目没有要求使用技术点,你是否知道还有哪些方式可以实现这个阻塞队列呢?
自定义阻塞队列编码如下:
bash
/**
* CustomBlockingQueue
* @author senfel
* @date 2024/2/21 12:50
*/
public class CustomBlockingQueue<T> {
private final LinkedList<T> queue = new LinkedList<>();
private final int capacity;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
public CustomBlockingQueue(int capacity) {
this.capacity = capacity;
}
/**
* put
* @author senfel
* @date 2024/2/21 12:52
* @return void
*/
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await();
}
if (queue.isEmpty()) {
notEmpty.signal(); // 如果之前有等待的消费者,现在可以唤醒了
}
queue.addLast(item);
} finally {
lock.unlock();
}
}
/**
* take
* @author senfel
* @date 2024/2/21 12:59
* @return void
*/
public T take() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
if (queue.size() == capacity) {
notFull.signal(); // 如果之前有等待的生产者,现在可以唤醒了
}
return queue.removeFirst();
} finally {
lock.unlock();
}
}
public boolean isEmpty() {
lock.lock();
try {
return queue.isEmpty();
} finally {
lock.unlock();
}
}
public boolean isFull() {
lock.lock();
try {
return queue.size() == capacity;
} finally {
lock.unlock();
}
}
}
写在最后
这个CustomBlockingQueue类使用了Java并发包中的ReentrantLock作为锁机制,并通过其条件变量notEmpty和notFull分别管理队列空和队列满的情况。当队列满时,调用put方法会阻塞,直到有空间;当队列空时,调用take方法也会阻塞,直到有元素可消费。同时,当添加或删除元素改变队列状态时,会适当调整对应的条件变量以唤醒等待的线程。