You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java基于Semaphore实现AABBAABB无限序列输出异常问题求助

问题背景

预期通过Java多线程+信号量实现无限循环输出AABBAABB字符序列,实际运行输出为AABBAAABB,与预期不符,原实现代码如下:

private static Semaphore semA = new Semaphore(2);
private static Semaphore semB = new Semaphore(0);

public static void main(String[] args) {
    while(true) {
        new P1A().start();
        new P2B().start();
        try {
            Thread.sleep(1000);
        }
        catch(InterruptedException e) {
            e.printStackTrace();
        }
    }

}

static class P1A extends Thread{
    
    public void run() {
        try {
            semA.acquire();
            System.out.print("A");
            if(semA.availablePermits() == 0) {
                semB.release(2);
            }
        }
        catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
}

static class P2B extends Thread{
    
    public void run() {
        try {
            semB.acquire();
            System.out.print("B");
            if(semB.availablePermits() == 0)
                semA.release(2);
        }
        catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
}
根因分析

原代码存在两个核心问题,直接导致输出序列混乱:

  • 竞态条件导致信号量许可数异常:Semaphore.availablePermits()仅返回调用瞬间的许可瞬时值,本身是为监控、排查场景设计的,不适合作为业务流程控制的判断条件。原逻辑中两个打印A的线程可能出现如下执行顺序:
    1. 线程A1获取semA许可,打印A后还未执行许可数判断,发生线程上下文切换
    2. 线程A2获取semA剩余的1个许可,打印A,此时判断semA许可数为0,释放2个semB许可
    3. 上下文切回A1,A1判断semA许可数同样为0,也会执行semB释放2个许可的逻辑,最终semB累计得到4个许可
      许可数错乱后,后续B线程打印完成时也会重复释放semA许可,最终导致连续打印3个甚至更多A/B,序列完全失控。
  • 线程创建逻辑错误:main线程在无限循环中每秒创建新的A、B线程,既会导致线程数无限膨胀最终触发OOM,也会因为线程创建、调度的延迟不确定性,进一步放大竞态问题,打乱打印顺序。
修复方案

针对上述问题做两点调整即可得到预期输出:

  1. 取消循环创建线程的逻辑,固定启动2个A打印线程、2个B打印线程,打印逻辑放在线程内部的无限循环中执行,避免线程无限制创建,减少调度不确定性。
  2. 用原子计数器替代availablePermits()做流程判断,通过原子操作保证仅当连续打印满2个同类型字符时,才触发对面信号量的许可释放,从根源上避免多线程重复释放许可的竞态问题。

修复后可正常运行的代码如下:

import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

public class PrintABSeq {
    private static Semaphore semA = new Semaphore(2);
    private static Semaphore semB = new Semaphore(0);
    // 连续打印A的计数器
    private static AtomicInteger countA = new AtomicInteger(0);
    // 连续打印B的计数器
    private static AtomicInteger countB = new AtomicInteger(0);

    public static void main(String[] args) {
        // 固定启动2个A线程、2个B线程
        for (int i = 0; i < 2; i++) {
            new P1A().start();
            new P2B().start();
        }
    }

    static class P1A extends Thread{
        @Override
        public void run() {
            while (true) {
                try {
                    semA.acquire();
                    System.out.print("A");
                    // 原子累加计数,仅当累计到2个A时释放B的许可,重置计数器
                    if (countA.incrementAndGet() == 2) {
                        semB.release(2);
                        countA.set(0);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class P2B extends Thread{
        @Override
        public void run() {
            while (true) {
                try {
                    semB.acquire();
                    System.out.print("B");
                    // 原子累加计数,仅当累计到2个B时释放A的许可,重置计数器
                    if (countB.incrementAndGet() == 2) {
                        semA.release(2);
                        countB.set(0);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者BeArt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 06:57:19