多线程计数器

35 阅读3分钟

单体服务,多线程共享计数器,不使用分布式锁保证线程安全。 对于多线程环境下的共享计数器,选择AtomicLong还是更好的选择,因为它能保证线程安全且具有更好的性能特性。

详细分析

1. 线程安全比较

AtomicLong

  • 使用 CAS(Compare and Swap)操作保证原子性
  • 提供完整的原子操作 API(getAndAdd、addAndGet 等)
  • 完全线程安全

LongAdder

  • 本质上是 AtomicLong 的优化版本
  • 内部使用多个 Cell 分段存储,减少竞争
  • 同样线程安全

2. 性能对比

java

运行

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

public class CounterPerformanceTest {
    private static final int THREADS = 16;
    private static final int OPERATIONS = 1000000;
    
    public static void main(String[] args) throws InterruptedException {
        // 测试AtomicLong
        long atomicTime = testCounter(new AtomicLongCounter());
        System.out.println("AtomicLong: " + atomicTime + " ms");
        
        // 测试LongAdder
        long adderTime = testCounter(new LongAdderCounter());
        System.out.println("LongAdder: " + adderTime + " ms");
    }
    
    private static long testCounter(Counter counter) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(THREADS);
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < THREADS; i++) {
            executor.submit(() -> {
                for (int j = 0; j < OPERATIONS; j++) {
                    counter.add(1);
                }
            });
        }
        
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        
        long endTime = System.currentTimeMillis();
        System.out.println("Final count: " + counter.get());
        
        return endTime - startTime;
    }
    
    interface Counter {
        void add(long delta);
        long get();
    }
    
    static class AtomicLongCounter implements Counter {
        private final AtomicLong count = new AtomicLong(0);
        
        @Override
        public void add(long delta) {
            count.addAndGet(delta);
        }
        
        @Override
        public long get() {
            return count.get();
        }
    }
    
    static class LongAdderCounter implements Counter {
        private final LongAdder count = new LongAdder();
        
        @Override
        public void add(long delta) {
            count.add(delta);
        }
        
        @Override
        public long get() {
            return count.sum();
        }
    }
}

3. 使用场景分析

AtomicLong 适合:

  • 中等并发场景
  • 需要精确计数的场景
  • 需要完整原子操作 API 的场景

LongAdder 适合:

  • 高并发场景(大量线程同时更新)
  • 写操作远多于读操作的场景
  • 对实时性要求不高的场景

4. 额度计数器实现示例

java

运行

import java.util.concurrent.atomic.AtomicLong;

public class SharedQuotaCounter {
    private final AtomicLong quota;
    private final long maxQuota;
    
    public SharedQuotaCounter(long initialQuota, long maxQuota) {
        this.quota = new AtomicLong(initialQuota);
        this.maxQuota = maxQuota;
    }
    
    /**
     * 尝试增加额度
     * @param amount 要增加的额度
     * @return 是否成功(是否超过超过最大额度)
     */
    public synchronized
    public boolean tryAddQuota(long amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        
        long current;
        long newValue;
        
        do {
            current = quota.get();
            newValue = current + amount;
            
            // 检查超过最大额度
            if (newValue > maxQuota) {
                return false;
            }
            
            // 使用CAS操作更新
        } while (!quota.compareAndSet(current, newValue));
        
        return true;
    }
    
    /**
     * 增加额度(不检查最大限制)
     * @param amount 要增加的额度
     */
    public void addQuotaUnchecked(long amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        quota.addAndGet(amount);
    }
    
    /**
     * 获取当前额度
     */
    public long getCurrentQuota() {
        return quota.get();
    }
    
    /**
     * 重置额度
     */
    public void resetQuota(long newQuota) {
        quota.set(newQuota);
    }
    
    /**
     * 减少额度
     */
    public void subtractQuota(long amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        
        long current;
        long newValue;
        
        do {
            current = quota.get();
            newValue = current - amount;
            
            // 防止出现负数
            if (newValue < 0) {
                newValue = 0;
            }
            
        } while (!quota.compareAndSet(current, newValue));
    }
    
    public long getMaxQuota() {
        return maxQuota;
    }
    
    public boolean hasRemainingQuota() {
        return quota.get() < maxQuota;
    }
}

5. 使用示例

java

运行

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        // 创建额度计数器,初始额度0,最大额度1000
        SharedQuotaCounter counter = new SharedQuotaCounter(0, 1000);
        
        ExecutorService executor = Executors.newFixedThreadPool(8);
        
        // 启动多个线程增加额度
        for (int i = 0; i < 8; i++) {
            final int threadId = i;
            executor.submit(() -> {
                for (int j = 0; j < 200; j++) {
                    long amount = ThreadLocalRandom.current().nextLong(1, 10);
                    boolean success = counter.tryAddQuota(amount);
                    
                    if (success) {
                        System.out.printf("Thread %d: Added %d, Current: %d%n", 
                            threadId, amount, counter.getCurrentQuota());
                    } else {
                        System.out.printf("Thread %d: Failed to add %d, Max reached%n", 
                            threadId, amount);
                        break;
                    }
                    
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        return;
                    }
                }
            });
        }
        
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        
        System.out.println("Final quota: " + counter.getCurrentQuota());
        System.out.println("Max quota: " + counter.getMaxQuota());
    }
}

结论

推荐使用 AtomicLong,原因:

  1. 线程安全保证:完整的原子操作支持
  2. 性能优秀:在大多数并发场景下表现良好
  3. API 丰富:提供各种原子操作方法
  4. 内存一致性:保证 volatile 语义

只有在以下情况下考虑 LongAdder:

  • 极高并发(数十个以上线程同时更新)
  • 写操作远多于读操作
  • 对实时性要求不高

对于额度计数器这种需要精确控制和频繁读取的场景,AtomicLong 是更稳妥的选择。