Java如何实现三个生产者线程与一个消费者线程的指定顺序同步
问题核心
你当前实现的Store类同步粒度是单字符级,每次消费者消费完单个字符就会唤醒所有生产者,任意生产者抢到锁即可生产下一个字符,无法保证单个生产者连续输出完整单词的所有字符,因此达不到你需要的执行顺序要求。
改造方案
我们需要新增状态控制生产权限,确保单个生产者生产完完整单词后才移交权限给下一个生产者,改造后的代码如下:
首先用1/2/3作为三个生产者的唯一标识,给Store新增两个状态变量:
currentAllowedProducer:当前允许生产的生产者ID,初始值为1,按1→2→3→1的顺序循环切换currentWordFinished:标记当前生产者是否已经完成单个单词的所有字符生产,初始为false
修改后的完整Store类代码
public class Store { public static final char CONSUMER_FREE = '\0'; private volatile char consumer = CONSUMER_FREE; private final ThreadPoolExecutor executor; // 新增状态:当前允许生产的生产者ID,1/2/3循环 private int currentAllowedProducer = 1; // 新增状态:当前生产者是否已生产完完整单词 private boolean currentWordFinished = false; public Store(ThreadPoolExecutor executor) { this.executor = executor; } /** * 生产方法新增两个入参: * @param producerId 生产者ID,固定取值1/2/3 * @param isEndOfWord 当前字符是否是当前单词的最后一个字符 */ public synchronized void produce(char c, int producerId, boolean isEndOfWord) { // 等待条件:缓冲区满,或者当前调用的生产者没有生产权限 while (isConsumerBussy() || producerId != currentAllowedProducer) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } consumer = c; // 如果是单词最后一个字符,标记完成,切换下一个允许生产的生产者 if (isEndOfWord) { currentWordFinished = true; currentAllowedProducer = currentAllowedProducer % 3 + 1; } notify(); // 唤醒消费者消费当前字符 } public synchronized char consume() throws StoreProducersRip { while (isConsumerFree()) { try { wait(250); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } try { return consumer; } finally { freeConsumer(); // 当前单词生产完成时唤醒所有生产者,否则只唤醒当前持有权限的生产者继续生产下一个字符 if (currentWordFinished) { currentWordFinished = false; notifyAll(); } else { notify(); } } } private void freeConsumer() { consumer = CONSUMER_FREE; } private boolean isConsumerBussy() { return consumer != CONSUMER_FREE; } private boolean isConsumerFree() throws StoreProducersRip { if (executor.getActiveCount() == 0) throw new StoreProducersRip(); return consumer == CONSUMER_FREE; } }
生产者侧使用说明
你需要在生产者的业务逻辑中判断当前读取到的字符是不是单词的最后一个字符(比如遇到空格、换行、文件结束符时标记为单词结束),调用produce方法时传入对应的producerId和isEndOfWord参数即可。改造后会严格按照你期望的顺序执行:Producer1完整输出Word1所有字符后,权限移交给Producer2输出word1,之后移交Producer3输出word1,再切回Producer1输出Word2,以此类推。
内容的提问来源于stack exchange,提问作者Bartosz Różycki
相关产品推荐
相关产品推荐

