生产者消费者实现中列表超容及索引越界问题求助
1. 货架偶发超容的核心原因
你搞反了Semaphore的释放逻辑,这是超容的主要诱因。正确的生产者消费者Semaphore分工应该是:
- 生产者持有的Semaphore(代表空货架数量),初始值设为maxCapacity(5),生产者获取许可后,这个许可要由消费者释放——消费者拿走商品空出位置后,把许可还给生产者,才能让新的生产者继续添加。
- 消费者持有的Semaphore(代表可消费商品数量),初始值设为0,消费者获取许可后,这个许可要由生产者释放——生产者添加商品后,增加可消费数量,让消费者能获取许可取货。
如果你的代码里,生产者添加商品后错误地把许可还给了自己的Semaphore,而不是消费者的,那生产者的Semaphore许可数永远不会真正减少:比如初始5个许可,一个生产者acquire后变成4,添加完又release变回5,相当于许可被重复利用,后续生产者可以不断获取许可添加商品,直接导致货架size超过上限。
退一步说,就算Semaphore逻辑没错,普通List的size()判断和add()操作不是原子的。比如货架当前size是4,两个生产者线程同时通过Semaphore校验(拿到许可),其中一个线程还没完成add操作,另一个线程已经读取到过期的size=4值,接着执行add,最终货架size就会变成6——因为List的size变量本身不是线程安全的,多线程下读取的数值可能和实际情况不符。
2. IndexOutOfBoundsException的原因
普通List是非线程安全的,没有同步机制:
- 多个消费者线程同时执行remove操作时会出现竞态条件:比如货架有1个商品,第一个消费者已经remove掉,size变成0,但第二个消费者已经读取到size=1的过期值,仍尝试remove(0),直接抛出索引越界异常。
- 甚至List内部的数组结构可能因为多线程并发修改被损坏,导致size计数失真,进一步触发异常。
修正Semaphore的使用逻辑
严格遵循生产者消费者的Semaphore分工:- 生产者:先获取空货架许可(
emptySemaphore.acquire()),添加商品后,释放可消费许可(fullSemaphore.release())。 - 消费者:先获取可消费许可(
fullSemaphore.acquire()),移除商品后,释放空货架许可(emptySemaphore.release())。
- 生产者:先获取空货架许可(
替换为线程安全的容器实现
把普通List换成ConcurrentLinkedQueue(如果货架是队列结构,生产者从尾部加、消费者从头部取,天然适配生产者消费者模型),或者CopyOnWriteArrayList(适合读多写少的场景)。可选:用ReentrantLock加锁
如果需要对货架操作做更精细的控制,可以给Store类的add/remove方法加上ReentrantLock,保证每次只有一个线程能修改货架,彻底避免竞态条件。
修正后的核心代码示例
// Store类 public class Store { private final int maxShelfs; private final Queue<Object> shelf = new ConcurrentLinkedQueue<>(); public Store(int maxShelfs) { this.maxShelfs = maxShelfs; } public boolean addGoods(Object goods) { if (shelf.size() >= maxShelfs) { return false; } return shelf.offer(goods); } public Object removeGoods() { return shelf.poll(); } } // 生产者线程 class Producer implements Runnable { private final Store store; private final Semaphore emptySemaphore; private final Semaphore fullSemaphore; public Producer(Store store, Semaphore emptySemaphore, Semaphore fullSemaphore) { this.store = store; this.emptySemaphore = emptySemaphore; this.fullSemaphore = fullSemaphore; } @Override public void run() { while (true) { try { emptySemaphore.acquire(); Object goods = new Object(); // 模拟生成商品 if (store.addGoods(goods)) { System.out.println("生产者添加商品,当前货架数量:" + store.shelf.size()); fullSemaphore.release(); } Thread.sleep(100); // 模拟生产耗时 } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } } // 消费者线程 class Consumer implements Runnable { private final Store store; private final Semaphore emptySemaphore; private final Semaphore fullSemaphore; public Consumer(Store store, Semaphore emptySemaphore, Semaphore fullSemaphore) { this.store = store; this.emptySemaphore = emptySemaphore; this.fullSemaphore = fullSemaphore; } @Override public void run() { while (true) { try { fullSemaphore.acquire(); Object goods = store.removeGoods(); if (goods != null) { System.out.println("消费者移除商品,当前货架数量:" + store.shelf.size()); emptySemaphore.release(); } Thread.sleep(200); // 模拟消费耗时 } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } }
内容的提问来源于stack exchange,提问作者ReD

