在多线程并发编程的实际开发中,多个线程同时对同一个整型变量执行自增操作是非常常见的场景,但普通的自增操作并不具备原子性,很容易出现最终结果小于预期的情况,这就是典型的线程安全问题。要解决这个问题,开发者可以选择不同的方案,其中原子变量类是兼顾性能和正确性的优选方案。

普通变量自增的线程安全问题演示
我们先来看一个不使用任何同步措施的自增示例,启动10个线程,每个线程对同一个整型变量执行1000次自增操作,预期最终结果应该是10000。
public class NormalIncrementTest {
// 普通整型变量
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
// 创建10个线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
// 每个线程自增1000次
for (int j = 0; j < 1000; j++) {
count++;
}
}).start();
}
// 等待所有线程执行完成
Thread.sleep(2000);
// 输出最终结果
System.out.println("最终count值:" + count);
}
}
多次运行上述代码,你会发现最终结果往往小于10000,这是因为count++操作实际上分为三步:读取count当前值、将值加1、写回新的count值。多个线程同时执行时,可能出现两个线程读取到同一个旧值,各自加1后写回,导致一次自增操作被覆盖,最终结果偏小。
传统解决线程安全自增的方案及不足
最常用的传统方案是使用synchronized关键字对自增操作加锁,保证同一时间只有一个线程能执行自增操作。
public class SynchronizedIncrementTest {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
// 使用synchronized加锁
synchronized (SynchronizedIncrementTest.class) {
count++;
}
}
}).start();
}
Thread.sleep(2000);
System.out.println("最终count值:" + count);
}
}
这种方式能够保证结果正确,但synchronized是重量级锁,线程获取不到锁时会进入阻塞状态,涉及用户态和内核态的切换,性能开销比较大,在高并发场景下会成为性能瓶颈。
原子变量类解决自增线程安全的原理
原子变量类位于java.util.concurrent.atomic包下,比如AtomicInteger就是针对整型变量的原子类,它的核心是基于CAS(Compare And Swap,比较并交换)机制实现的。
CAS操作包含三个核心参数:内存地址V、旧的预期值A、要修改的新值B。操作时先比较内存地址V上的值是否等于预期值A,如果相等,就把V的值更新为B,否则不做任何操作,整个过程是原子性的。
AtomicInteger的自增方法incrementAndGet就是通过循环CAS实现的:先获取当前值,计算加1后的新值,然后调用CAS尝试更新,如果更新失败(说明有其他线程修改了值),就重新获取当前值重试,直到更新成功。
原子变量类实战解决自增线程安全问题
我们用AtomicInteger来改写前面的自增示例,代码如下:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIncrementTest {
// 使用原子整型变量,初始化为0
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
// 调用原子自增方法,相当于count++
count.incrementAndGet();
}
}).start();
}
Thread.sleep(2000);
// 获取最终值
System.out.println("最终count值:" + count.get());
}
}
运行上述代码,你会发现最终结果始终是10000,完全符合预期。这种方式不需要加锁,线程不会阻塞,性能比synchronized方案更好,同时保证了操作的原子性。
原子变量类的常用方法说明
除了incrementAndGet方法,AtomicInteger还有很多实用的方法,常用方法如下:
| 方法名 | 作用说明 |
|---|---|
| get() | 获取当前变量的值 |
| set(int newValue) | 设置变量为新的值 |
| incrementAndGet() | 变量自增1,返回自增后的值 |
| getAndIncrement() | 变量自增1,返回自增前的值 |
| decrementAndGet() | 变量自减1,返回自减后的值 |
| addAndGet(int delta) | 变量加上指定的delta值,返回相加后的值 |
| compareAndSet(int expect, int update) | 如果当前值等于expect,就更新为update,返回是否更新成功 |
使用原子变量类的注意事项
- 原子变量类只能保证单个变量的操作是原子性的,如果需要保证多个变量的组合操作原子性,还是需要使用锁或者其他同步机制。
- CAS操作在高竞争场景下会出现大量的重试,可能会消耗一定的CPU资源,这种情况下需要评估是否适合使用原子变量类。
- 除了
AtomicInteger,该包下还有AtomicLong、AtomicBoolean、AtomicReference等,分别对应长整型、布尔型、引用类型的原子操作,可根据实际场景选择。
原子变量类是多线程场景下处理单个变量原子操作的高效方案,相比传统的锁机制有更好的性能表现,开发者在实际开发中遇到类似的自增、自减等线程安全问题,可以优先考虑使用原子变量类解决。