Java多线程场景下静态volatile变量自增出现重复值如何解决?
问题根本原因
n++ 本质是读-改-写三个非原子操作的组合:
- 从主内存读取
n的当前值 - 在线程工作内存执行+1计算
- 将计算后的新值写回主内存
volatile关键字仅能保证变量的可见性(每次读取直接取主内存最新值、修改后立刻同步回主内存),但无法保证这三步操作的原子性。多线程并发执行时会出现多个线程拿到同一个n的取值,各自自增后写回主内存,最终产生重复编号。
可行解决方案
方案1:使用java.util.concurrent.atomic包下的原子类(推荐,无锁实现性能高)
将static volatile int n=0替换为AtomicInteger类型,调用其原子自增方法实现编号生成,底层通过CAS实现操作原子性,同时满足可见性要求。
修改后的代码示例:
public class Producer implements Runnable{ private String str; private Fifo f; private int i; // 替换为原子整型,默认初始值为0 private static AtomicInteger n = new AtomicInteger(0); public Producer(String str,int i,Fifo f) .... public void run() { try { this.go(); } catch (InterruptedException e) { ; } } void go() throws InterruptedException { while(true) { Thread.sleep(i); // 原子操作:先获取当前值再自增,等价原有n++的逻辑 int l = n.getAndIncrement(); String k=str+" "+l+" "; f.put(k); System.out.println("produced "+str+" "+l+" "+System.currentTimeMillis()%100000); } } }
方案2:对自增操作加互斥锁(实现简单,适合并发量不高的场景)
通过synchronized关键字对自增逻辑加锁,保证同一时间只有一个线程能执行自增操作,避免并发冲突,代码示例:
public class Producer implements Runnable{ private String str; private Fifo f; private int i; private static volatile int n=0; // 定义全局锁对象 private static final Object COUNT_LOCK = new Object(); public Producer(String str,int i,Fifo f) .... public void run() { try { this.go(); } catch (InterruptedException e) { ; } } void go() throws InterruptedException { while(true) { Thread.sleep(i); int l; // 加锁保证自增操作原子性 synchronized (COUNT_LOCK) { l = n++; } String k=str+" "+l+" "; f.put(k); System.out.println("produced "+str+" "+l+" "+System.currentTimeMillis()%100000); } } }
方案3:使用LongAdder类(JDK1.8+适用,超高并发场景性能更优)
如果生产环境并发量极高,可以使用LongAdder替换AtomicInteger,它通过分段计数的方式降低CAS操作的竞争频率,高并发场景下性能优于AtomicInteger。
内容的提问来源于stack exchange,提问作者Makkador
相关产品推荐
相关产品推荐

