在Java多线程编程中,计数和限流是非常高频的需求,比如统计接口调用次数、限制单位时间内的请求量等。如果直接使用普通的int变量进行计数,在多线程并发修改时会出现竞态条件,导致计数结果不准确,进而让限流逻辑失效。因此需要通过合理的方式实现线程安全的计数,再基于计数能力完成限流功能。

线程安全计数的实现方案
1. 使用AtomicInteger原子类
Java并发包中的AtomicInteger是基于CAS(Compare And Swap)机制实现的原子操作类,能够在无锁的情况下保证计数的线程安全,性能通常优于同步锁方案。
以下是使用AtomicInteger实现线程安全计数的示例:
import java.util.concurrent.atomic.AtomicInteger;
public class SafeCounter {
// 初始化计数为0
private AtomicInteger count = new AtomicInteger(0);
// 自增计数并返回自增后的值
public int increment() {
return count.incrementAndGet();
}
// 获取当前计数
public int getCount() {
return count.get();
}
// 重置计数
public void reset() {
count.set(0);
}
public static void main(String[] args) throws InterruptedException {
SafeCounter counter = new SafeCounter();
// 创建10个线程,每个线程自增1000次
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
threads[i].start();
}
// 等待所有线程执行完成
for (Thread thread : threads) {
thread.join();
}
// 输出最终计数,结果应为10000
System.out.println("最终计数:" + counter.getCount());
}
}
2. 使用synchronized同步锁
如果不想使用原子类,也可以通过synchronized关键字对计数操作加锁,保证同一时间只有一个线程能修改计数变量,从而实现线程安全。
示例代码如下:
public class SyncCounter {
private int count = 0;
// 对自增方法加锁
public synchronized void increment() {
count++;
}
// 获取计数也需要加锁,避免读到未刷新的缓存值
public synchronized int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
SyncCounter counter = new SyncCounter();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("最终计数:" + counter.getCount());
}
}
基于线程安全计数实现限流
常见的限流场景是限制单位时间内的请求次数,比如1秒内最多允许100次请求。我们可以结合线程安全的计数和时间判断来实现简单的固定窗口限流。
以下是基于AtomicInteger实现的固定窗口限流示例:
import java.util.concurrent.atomic.AtomicInteger;
public class SimpleRateLimiter {
// 限流阈值,单位时间允许的最大请求数
private final int limit;
// 单位时间长度,单位毫秒
private final long windowMillis;
// 当前窗口内的计数
private AtomicInteger count;
// 当前窗口的起始时间
private long windowStart;
public SimpleRateLimiter(int limit, long windowMillis) {
this.limit = limit;
this.windowMillis = windowMillis;
this.count = new AtomicInteger(0);
this.windowStart = System.currentTimeMillis();
}
// 尝试获取请求许可,返回true表示允许请求,false表示被限流
public boolean tryAcquire() {
long now = System.currentTimeMillis();
// 判断当前是否在窗口内
if (now - windowStart < windowMillis) {
// 窗口内计数自增,判断是否超过阈值
int currentCount = count.incrementAndGet();
return currentCount <= limit;
} else {
// 窗口过期,重置窗口和计数
synchronized (this) {
// 双重检查,避免多个线程同时重置
if (now - windowStart >= windowMillis) {
windowStart = now;
count.set(0);
}
}
// 重置后重新尝试获取
return tryAcquire();
}
}
public static void main(String[] args) throws InterruptedException {
// 1秒内最多允许5次请求
SimpleRateLimiter limiter = new SimpleRateLimiter(5, 1000);
// 模拟10次请求
for (int i = 0; i < 10; i++) {
boolean allowed = limiter.tryAcquire();
System.out.println("第" + (i + 1) + "次请求:" + (allowed ? "允许" : "被限流"));
Thread.sleep(100);
}
}
}
两种计数方案的选择建议
如果计数场景是简单的自增、自减操作,优先选择AtomicInteger,它的无锁特性在高并发场景下性能更好。如果计数逻辑比较复杂,需要多个变量配合修改,或者需要保证多个操作的整体原子性,使用synchronized同步锁会更合适,代码逻辑也更清晰。
对于限流场景,上述固定窗口限流实现比较简单,但存在窗口边界突刺的问题,如果需要更平滑的限流效果,可以结合AtomicInteger实现滑动窗口限流,或者使用令牌桶、漏桶等更成熟的限流算法,不过核心的计数部分依然需要保证线程安全。
Java线程安全计数限流AtomicInteger修改时间:2026-06-30 15:36:31