面试题:你使用过 Java 的累加器吗?

Java 中的 累加器(Accumulator) 是 java.util.concurrent.atomic 包中提供的一种高效的工具,用于在多线程环境下进行累加操作。累加器的主要实现类是 LongAdder 和 DoubleAdder,它们在高并发场景下比传统的 AtomicLong 和 AtomicDouble 性能更好。


1. 累加器的背景

  • 在高并发场景下,使用 AtomicLong 或 AtomicDouble 进行累加操作时,可能会因为大量线程竞争同一个变量而导致性能下降。
  • 累加器通过将单个变量拆分为多个变量(称为 Cell),每个线程操作自己的 Cell,从而减少竞争,提高性能。
  • 在需要获取最终结果时,累加器会将所有 Cell 的值累加起来。

2. LongAdder

  • 特点
    • 用于对 long 类型进行高效的累加操作。
    • 适合高并发场景下的计数器、统计等需求。
    • 性能优于 AtomicLong,但在低并发场景下可能不如 AtomicLong
  • 常用方法
    • add(long x):累加一个值。
    • increment():累加 1。
    • decrement():累减 1。
    • sum():返回当前的总和。
    • reset():重置累加器为 0。
    • sumThenReset():返回当前的总和并重置累加器为 0。
  • 示例代码
    import java.util.concurrent.atomic.LongAdder;
    
    public class LongAdderExample {
        public static void main(String[] args) {
            LongAdder adder = new LongAdder();
    
            // 多个线程并发累加
            Runnable task = () -> {
                for (int i = 0; i < 1000; i++) {
                    adder.increment();
                }
            };
    
            Thread thread1 = new Thread(task);
            Thread thread2 = new Thread(task);
            thread1.start();
            thread2.start();
    
            try {
                thread1.join();
                thread2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("Final sum: " + adder.sum()); // 输出 2000
        }
    }

3. DoubleAdder

  • 特点
    • 用于对 double 类型进行高效的累加操作。
    • 与 LongAdder 类似,适合高并发场景下的浮点数累加。
  • 常用方法
    • add(double x):累加一个值。
    • sum():返回当前的总和。
    • reset():重置累加器为 0。
    • sumThenReset():返回当前的总和并重置累加器为 0。
  • 示例代码
    import java.util.concurrent.atomic.DoubleAdder;
    
    public class DoubleAdderExample {
        public static void main(String[] args) {
            DoubleAdder adder = new DoubleAdder();
    
            // 多个线程并发累加
            Runnable task = () -> {
                for (int i = 0; i < 1000; i++) {
                    adder.add(1.5);
                }
            };
    
            Thread thread1 = new Thread(task);
            Thread thread2 = new Thread(task);
            thread1.start();
            thread2.start();
    
            try {
                thread1.join();
                thread2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("Final sum: " + adder.sum()); // 输出 3000.0
        }
    }

4. 累加器与 AtomicLong 的对比

  • 性能
    • 在高并发场景下,LongAdder 和 DoubleAdder 的性能优于 AtomicLong 和 AtomicDouble,因为它们减少了线程竞争。
    • 在低并发场景下,AtomicLong 和 AtomicDouble 的性能可能更好,因为它们没有额外的 Cell 拆分开销。
  • 适用场景
    • 累加器:适合高并发场景下的计数器、统计等需求。
    • AtomicLong:适合低并发场景或需要精确原子操作的场景。

5. 总结

  • LongAdder 和 DoubleAdder 是 Java 中高效的累加器实现,适合高并发场景下的累加操作。
  • 它们通过减少线程竞争来提高性能,但在低并发场景下可能不如 AtomicLong 和 AtomicDouble
  • 如果需要在高并发环境下进行累加操作,累加器是一个非常好的选择。

6. 扩展:LongAccumulator 和 DoubleAccumulator

  • LongAccumulator 和 DoubleAccumulator 是更通用的累加器实现,允许自定义累加函数。
  • 示例代码
    import java.util.concurrent.atomic.LongAccumulator;
    
    public class LongAccumulatorExample {
        public static void main(String[] args) {
            LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y, 0);
    
            // 多个线程并发累加
            Runnable task = () -> {
                for (int i = 0; i < 1000; i++) {
                    accumulator.accumulate(1);
                }
            };
    
            Thread thread1 = new Thread(task);
            Thread thread2 = new Thread(task);
            thread1.start();
            thread2.start();
    
            try {
                thread1.join();
                thread2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("Final sum: " + accumulator.get()); // 输出 2000
        }
    }
  • 适用场景
    • 适合需要自定义累加逻辑的场景,如求最大值、最小值等。
THE END
点赞12 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容