Java线程安全计数器越界问题:如何修正计数超出上限异常?
嘿,我来帮你捋捋这个线程安全计数器的问题~ 你遇到的计数跑到51、52的情况,核心原因是竞态条件——虽然你的printAndIncrement是静态同步方法,但while循环的条件判断和调用这个方法之间,存在一个“时间窗口”,多个线程可能同时通过了currentValue < 目标值的判断,然后排队进入同步方法执行递增,自然就会超过预期数值。
先回答你的核心问题:要不要在while循环外加synchronized?
不建议直接给while循环整个套synchronized,这样会让所有线程串行执行,完全失去多线程的意义。更合理的做法是把条件判断和递增操作合并到同一个同步上下文里,确保判断+递增的逻辑是原子性的。
具体的修复方案
方案1:把条件判断移入同步方法(简单直接)
修改你的printAndIncrement方法,让它先在同步环境里判断条件,只有满足时才执行打印和递增:
public class MyCounter implements Runnable { static int currentValue = 0; private static final int MAX_COUNT = 50; // 定义目标最大值,更清晰 private static synchronized void printAndIncrement() { if (currentValue < MAX_COUNT) { System.out.println(currentValue); currentValue++; } } @Override public void run() { // 循环调用直到计数达标 while (currentValue < MAX_COUNT) { printAndIncrement(); // 可选:加小休眠避免空转占用CPU try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }
这样一来,每个线程进入同步方法时都会先检查条件,确保只有在计数未达标的情况下才执行递增,从根本上避免了多线程同时通过判断的问题。
方案2:用Java并发包的原子类(更优雅高效)
如果不想手动处理同步,可以用java.util.concurrent.atomic.AtomicInteger,它本身就是线程安全的,通过CAS(比较并交换)操作实现原子性的判断和更新,代码会更简洁:
public class MyCounter implements Runnable { private static final AtomicInteger currentValue = new AtomicInteger(0); private static final int MAX_COUNT = 50; @Override public void run() { int value; while ((value = currentValue.get()) < MAX_COUNT) { // CAS操作:如果当前值还是value,就更新为value+1 if (currentValue.compareAndSet(value, value + 1)) { System.out.println(value); } // 可选:加小休眠优化CPU占用 try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }
这个方案不需要显式加synchronized,利用原子类的特性保证线程安全,同时性能也比显式同步更好。
关于你提到的“双重检查”
你说的双重检查在这个场景下确实不够优雅,而且如果没有正确处理变量的可见性(比如给currentValue加volatile),双重检查本身可能存在逻辑漏洞。相比之下,上面两种方案要么简单可靠,要么更符合Java并发编程的最佳实践,比双重检查更值得推荐。
内容的提问来源于stack exchange,提问作者bbb

