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的线程可能出现如下执行顺序:- 线程A1获取semA许可,打印
A后还未执行许可数判断,发生线程上下文切换 - 线程A2获取semA剩余的1个许可,打印
A,此时判断semA许可数为0,释放2个semB许可 - 上下文切回A1,A1判断semA许可数同样为0,也会执行semB释放2个许可的逻辑,最终semB累计得到4个许可
许可数错乱后,后续B线程打印完成时也会重复释放semA许可,最终导致连续打印3个甚至更多A/B,序列完全失控。
- 线程A1获取semA许可,打印
- 线程创建逻辑错误:main线程在无限循环中每秒创建新的A、B线程,既会导致线程数无限膨胀最终触发OOM,也会因为线程创建、调度的延迟不确定性,进一步放大竞态问题,打乱打印顺序。
修复方案
针对上述问题做两点调整即可得到预期输出:
- 取消循环创建线程的逻辑,固定启动2个A打印线程、2个B打印线程,打印逻辑放在线程内部的无限循环中执行,避免线程无限制创建,减少调度不确定性。
- 用原子计数器替代
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
相关产品推荐
相关产品推荐

