邮箱里收到一个Github的职位
今天打开邮箱,看到一个Github的职位,还是第一次见到,有点新鲜感,点开看了看。因为正在玩儿DeepSeek Harness,我就把职位信息拿给了DeepSeek Harness,也顺便告知了一下我过去的几个工作岗位,让它给我出个编程面试题。
题目来了:
2026-08-18 · 有界阻塞队列(Bounded Blocking Queue)
背景
java.util.concurrent 里的 ArrayBlockingQueue / LinkedBlockingQueue 是工业级实现。
面试官爱问「自己写一个阻塞队列」,因为它能一次性考察:并发原语选型、内存可见性、
锁粒度、信号丢失(lost wakeup)、中断语义、公平性。
基本要求
- 不要用 JDK 的
BlockingQueue、ArrayBlockingQueue、LinkedBlockingQueue、ConcurrentLinkedQueue等现成实现,自己从零写。 - 实现下面这个接口(可自行改造成抽象类,语义要对齐):
public interface BoundedQueue<T> {
void put(T item) throws InterruptedException; // 队列满时阻塞
T take() throws InterruptedException; // 队列空时阻塞
int size(); // 当前元素个数
boolean isEmpty(); // 是否为空
}
- 有界:构造时传入
capacity > 0,put满时阻塞、take空时阻塞。 - 线程安全:多生产者 + 多消费者并发正确——不丢数据、不重复、不返回 null、
不越界、
size()不出现负值或超过容量。 - 真阻塞,禁止忙等:
while (!condition) Thread.sleep(...)或空自旋不算数。 - 中断语义:阻塞期间被
interrupt(),要抛InterruptedException并恢复 中断标志位(Thread.currentThread().interrupt())。
进阶(有余力就做)
- 超时版本:
boolean offer(T item, long timeout, TimeUnit unit)和T poll(long timeout, TimeUnit unit)。 - 公平性:等待最久的线程优先被唤醒(参考
ReentrantLock(true)的公平队列思想)。 - 双锁版:想一想能不能像
LinkedBlockingQueue那样用 put 锁 + take 锁 分开, 提高吞吐;代价是什么? - 思考题(不写代码):为什么
ArrayBlockingQueue用单锁 + 两个 Condition, 而LinkedBlockingQueue用双锁?各自的适用场景是什么?
评分维度
| 维度 | 关注点 |
|---|---|
| 正确性 | 并发下真的安全吗?内存可见性(volatile / lock)处理对了吗? |
| 阻塞/唤醒 | wait/notify 还是 Lock/Condition?while 循环检查条件写了吗(避免虚假唤醒 / lost wakeup)? |
| 中断 | 是否正确传播 InterruptedException、是否恢复中断标志? |
| 性能 | 锁粒度、size()/isEmpty() 能否无锁读取? |
| 测试 | 怎么证明它对?多线程压测、CountDownLatch 辅助、甚至 jcstress? |
我
题目是好题目,我是真的做不出来了,AI编程,害我不浅啊。
我先试了记事本手搓,写到Runnable时,稍微动摇了一下,它是不是java.util包的?嗯,不是,是java.lang包的,是吧。。。
这不行啊,还没到退休的时候呢。
还是先做题,哪怕是抄一遍
public class BoundedQueueImpl<T> Implements BoundedQueue<T> {
private final Object[] items; //用final,数组不可变,但引用本身没有final还是可以被改变的
private final Object lock = new Object(); //私有成员做锁跟this相比,至少可以避免被外人锁住,都不知道是谁干的
private int putIndex; //入队元素在数组中占哪个位置
private int takeIndex; //出队元素在数组中占哪个位置
private int count; //数组中有几个元素
private final int capacity; //我还是喜欢声明出来
public BoundedQueueImpl(int capacity) {
if(capacity <= 0) throw new IllegalArgumentException("Capacity should be > 0");
this.capacity = capacity;
}
@Override
public void put(T item) throws InterruptedException {
synchronized(lock) {
while(count == capacity) {//满了, #1号坑,不能用if,会越界
lock.wait();
}
item[putIndex] = item;
putIndex = (putIndex +1) % capacity;
count++;
lock.notifyAll(); //#2号坑,不能是notify,会死锁
}
}
@Override
@SuppressWarning("unchecked")
public T take() throws InterruptedException {
synchronized(lock) {
while(count == 0) {//空了,跟#1号坑一样,不能用if,会越界
lock.wait();
}
T item = (T)items[takeIndex];
items[takeIndex] = null;//好习惯得有,能尽早垃圾回收
takeIndex = (takeIndex+1) % capacity;
count--;
lock.notifyAll();//一样nofityAll
return item;
}
}
@Override
public int size() {
synchronized(lock) {//不能省,省了bug不断,指令重排,一致性。。。
return count;
}
}
@Override
public boolean isEmpty() {
synchronized(lock) {
return count == 0;
}
}
}
AI review
一、4 个 bug(3 个编译错 + 1 个致命漏写)
| 行 | 问题 | 改法 |
|---|---|---|
| 构造器 | items 压根没 new。private final Object[] items; 但构造器里没 items = new Object[capacity];,final 字段未初始化 → 编译不过;就算过了,一 put 就 NPE。这是唯一逻辑级的。 |
构造器里补 items = new Object[capacity]; |
| put | item[putIndex] = item; —— 把参数名 item 当数组用了 |
items[putIndex] = item; |
| 类声明 | Implements 大写 |
implements |
| 注解 | @SuppressWarning |
@SuppressWarnings |