我让DeepSeek Harness给我出了一个题

DeepSeek HarnessJava并发面试

邮箱里收到一个Github的职位

今天打开邮箱,看到一个Github的职位,还是第一次见到,有点新鲜感,点开看了看。因为正在玩儿DeepSeek Harness,我就把职位信息拿给了DeepSeek Harness,也顺便告知了一下我过去的几个工作岗位,让它给我出个编程面试题。

题目来了:

2026-08-18 · 有界阻塞队列(Bounded Blocking Queue)

背景

java.util.concurrent 里的 ArrayBlockingQueue / LinkedBlockingQueue 是工业级实现。 面试官爱问「自己写一个阻塞队列」,因为它能一次性考察:并发原语选型、内存可见性、 锁粒度、信号丢失(lost wakeup)、中断语义、公平性。

基本要求

  1. 不要用 JDK 的 BlockingQueue、ArrayBlockingQueue、LinkedBlockingQueue、 ConcurrentLinkedQueue 等现成实现,自己从零写。
  2. 实现下面这个接口(可自行改造成抽象类,语义要对齐):
public interface BoundedQueue<T> {
    void put(T item) throws InterruptedException;   // 队列满时阻塞
    T take() throws InterruptedException;           // 队列空时阻塞
    int size();                                     // 当前元素个数
    boolean isEmpty();                              // 是否为空
}
  1. 有界:构造时传入 capacity > 0,put 满时阻塞、take 空时阻塞。
  2. 线程安全:多生产者 + 多消费者并发正确——不丢数据、不重复、不返回 null、 不越界、size() 不出现负值或超过容量。
  3. 真阻塞,禁止忙等:while (!condition) Thread.sleep(...) 或空自旋不算数。
  4. 中断语义:阻塞期间被 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
← 返回博客