高并发下解决AtomicLong性能瓶颈的方案——LongAdder

LongAdder原理剖析
本文深入剖析了JDK1.8中的LongAdder类的工作原理,包括其内部结构、线程分配Cell机制、Cells数组初始化及扩容过程等,并探讨了如何解决线程间访问Cell元素的冲突。

一、 LongAdder简介

LongAdder类是JDK1.8新增的一个原子性操作类。上一节说到,AtomicLong通过CAS提供了非阻塞的原子性操作,相比用阻塞算法的synchronized来说性能已经得到了很大提升。在高并发下大量线程会同时竞争更新同一个原子变量,但由于只有一个线程的CAS操作会成功,这就造成了大量线程竞争失败后,会通过无限循环不断进行自旋尝试CAS操作,这会白白浪费CPU资源

为了解决AtomicLong在高并发下的缺点,LongAdder应运而生。LongAdder采用的思路是:既然AtomicLong由于过多线程同时去竞争同一个变量的更新而产生性能瓶颈,那么把一个变量分解为多个变量,让同样多的线程去竞争多个资源,就解决了性能问题。

如图4-1:

使用AtomicLong时,是多个线程同时竞争同一个原子变量。

如图4-2:

使用LongAdder时,则是内部维护多个Cell变量,每个Cell里面有一个初始值为0的long型变量,在同等并发量的情况下,争夺单个变量的线程会减少,这是变相地减少了争夺共享资源的并发量。另外,多个线程在争夺同一个Cell原子变量时候,如果失败并不是自旋CAS重试,而是尝试获取在其他Cell原子变量上进行CAS尝试,这增加了当前线程重试CAS成功的可能性。最后,在获取LongAdder当前值时,是把所有Cell变量的value值累加后再加上base值返回的。

LongAdder维护了一个Cells数组和一个基值变量base,Cells数组的特点如下:

  • 延迟初始化(默认情况下Cells为null)。因为Cells占用内存相对较大,故惰性加载。Cells为null时且并发线程较少时,所有的累加操作都是对base变量进行的。
  • 初始化时,Cells数组中Cell元素个数为2;同时,Cell数组中元素个数保存为2的N次方。

Cell 类是AtomicLong的一个改进,用来减少缓存的争用,即解决伪共享问题。对于大多数孤立的多个原子操作进行字节填充是浪费的,因为原子操作都是无规律地分散在内存中进行的,多个原子变量被放入同一个缓存行的可能性很小。但是原子性数组元素的内存地址是连续的,故数组内的多个元素能经常共享缓存行(伪共享),因此Cell类使用了@sun.misc.Contended注解进行字节填充,这是为了防止数组中多个元素共享一个缓存行,从而提升性能

二、LongAdder代码分析

为了解决高并发下多线程对一个变量 CAS 争夺失败后进行无限自旋而造成的降低并发性能的问题,LongAdder在内部维护了一个动态的Cell数组来分担对单个变量进行争夺的开销。

这一节我们来围绕以下话题对LongAdder的实现进行分析:

  • (1)LongAdder的结构
  • (2)当前线程应该访问Cells数组里的哪一个Cell元素
  • (3)如何初始化Cells数组
  • (4)Cells数组的扩容机制
  • (5)线程访问所分配的Cell元素有冲突后如何处理
  • (6)如何保证线程操作被分配的Cell元素的原子性

(1)LongAdder的结构

如图,LongAdder类继承自Striped64类:

Java中处理高并发场景时,通常采用以下几种解决方案和最佳实践,以确保系统的稳定性、性能和可扩展性。 ### 线程池管理 线程池是处理高并发场景的重要工具之一,它可以有效地管理线程资源,减少线程创建和销毁的开销。Java提供了`ThreadPoolExecutor`类来创建和管理线程池。通过配置核心线程数、最大线程数、队列容量等参数,可以灵活适应不同的并发需求。例如: ```java import java.util.concurrent.*; public class ThreadPoolExample { public static void main(String[] args) { int corePoolSize = 5; int maximumPoolSize = 10; long keepAliveTime = 5000; TimeUnit unit = TimeUnit.MILLISECONDS; BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(); ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue ); // 提交任务给线程池 for (int i = 0; i < 100; i++) { executor.execute(new Task()); } // 关闭线程池 executor.shutdown(); } static class Task implements Runnable { @Override public void run() { System.out.println("Task is running"); } } } ``` ### 无锁结构与并发工具类 Java并发包`java.util.concurrent.atomic`提供了多种无锁原子操作类,如`AtomicInteger`、`AtomicLong`等,它们可以在不使用锁的情况下保证线程安全,从而减少锁竞争带来的性能损耗。此外,`java.util.concurrent`包还提供了一系列高级并发工具类,如`CountDownLatch`、`CyclicBarrier`、`Semaphore`等,用于协调多个线程之间的协作。 ### 异步化处理 异步化是一种常见的提高系统吞吐量的方法。Java中可以通过`Future`和`Callable`接口实现异步调用,或者使用`CompletableFuture`进行更复杂的异步编程。此外,还可以利用消息队列(如RabbitMQ、Kafka)将耗时操作异步化,减轻主线程的压力。 ### 数据缓存 缓存是缓解数据库压力的有效手段。Java应用中可以使用本地缓存(如Guava Cache)或分布式缓存(如Redis、Memcached)来存储热点数据,减少对后端数据库的直接访问。合理设置缓存过期时间和更新策略,可以进一步提升系统性能。 ### 架构优化 合理的系统架构设计对于应对高并发同样至关重要。无状态服务的设计使得服务更容易水平扩展;分库分表技术可以分散单个数据库的压力;读写分离则能够分别优化读取和写入操作的性能。这些架构上的优化措施应当结合具体业务场景进行选择和实施。 ### 总结 综上所述,Java中处理高并发场景的解决方案包括但不限于线程池管理、无锁结构与并发工具类的应用、异步化处理、数据缓存以及架构层面的优化。通过综合运用这些技术和方法,可以显著提升系统的并发处理能力,保障服务的稳定性和响应速度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值