文章目录
高并发场景下CAS效率的优化
在高并发情况下,CAS操作的自旋重试会导致系统开销的增加,甚至有些线程可能进入一个无线重复的循环中。
除了存在CAS空自旋外,在SMP机构的CPU平台上,大量的CAS操作还可能导致
"总线风暴"
总线风暴是指在计算机系统中,由于大量的处理器或设备同时请求总线而导致的总线利用率骤增的现象。在多处理器系统或者高性能计算机中,当大量的处理器或者设备同时尝试访问系统总线时,可能会出现总线的瓶颈,导致系统性能下降或者崩溃。
总线风暴通常发生在以下情况下:
- 大量处理器竞争总线资源: 在多处理器系统中,如果大量的处理器同时竞争总线资源,比如同时发送读写请求或者数据传输请求,就会导致总线的繁忙,进而引发总线风暴。
- 外设访问频繁: 除了处理器外,系统中的其他外设,如存储设备、网络接口等,如果频繁地访问总线,也可能造成总线风暴。
总线风暴会带来严重的系统性能问题,包括但不限于:
- 性能下降: 总线风暴会导致总线资源的过度利用,从而降低了其他设备和处理器对总线的访问效率,系统的整体性能会因此下降。
- 数据丢失和冲突: 当多个设备同时请求总线时,可能会导致数据的丢失和冲突,影响数据的正确传输和处理。
- 系统稳定性问题: 如果总线风暴持续时间较长或者严重程度较高,可能会导致系统崩溃或者死锁等严重的稳定性问题。
主要影响的有一下几方面:
-
竞争激烈导致自旋时间延长: 当有多个线程同时竞争同一份资源时,只有一个线程能够成功执行CAS操作,其他线程将会不断进行自旋重试。如果竞争激烈,那么自旋时间会变得非常长,因为每个线程都需要等待前一个线程释放资源,才能尝试进行CAS操作。这样一来,自旋的时间延长会增加系统的开销,降低系统的性能。
-
上下文切换开销增加: 自旋重试过程中,如果一个线程在自旋过程中被抢占,需要进行上下文切换到其他线程,再切回来时会带来额外的开销。在高并发情况下,频繁的上下文切换会导致系统资源的浪费,降低系统的整体性能。
-
缓存竞争增加: 当多个线程同时竞争同一份资源时,由于缓存一致性协议的存在,会导致缓存行的竞争和缓存行的无效化。这会增加缓存访问的延迟和内存总线的压力,进而影响系统的性能。
-
对系统负载的影响: 随着自旋重试次数的增加,系统的负载也会增加。这会影响系统的响应速度和吞吐量,导致系统的整体性能下降。
1.空间换时间(LongAdder)
JDK8,提供了一个新的类,LongAdder,以空间换时间的方式提升高并发场景下CAS的操作性能。
LongAdder是Java并发包(java.util.concurrent)中提供的一种用于高并发环境下的原子累加器类型。它主要解决了在高并发情况下使用AtomicLong时可能出现的性能瓶颈问题。核心思想就是:热点分离
LongAdder的主要特点和优势包括:
-
分段累加: LongAdder内部采用了分段的方式来进行累加操作。它将累加器的值分成多个段,每个段有一个独立的累加器变量,称为"cell"。这样,在高并发情况下,不同线程可以同时对不同段的累加器进行操作,减少了线程竞争,提高了并发性能。(将value值分割成一个数组,当多线程访问时,通过Hash算法将线程映射到一个元素进行操作,从而获取value的结果,最终将数组的元素求和)
-
无锁操作: LongAdder的每个段都是独立的,因此在对各个段进行累加操作时,并不需要加锁。它使用了一种类似于CAS(Compare And Swap)的乐观锁机制,避免了使用全局锁带来的性能损耗。
-
高并发性能: 在高并发环境下,LongAdder的性能明显优于AtomicLong。由于采用了分段累加和无锁操作,LongAdder能够更好地应对大量线程同时对累加器进行操作的情况,从而提高了系统的并发性能。
-
自动扩容: 如果当前的线程数量超过了初始分段的数量,LongAdder会自动扩容,增加更多的段来适应更大的并发量,从而保持较高的性能。
-
统计汇总: LongAdder提供了
sum()
方法来获取所有段的累加器值的总和,方便对累加结果进行统计和汇总。
2.对比LongAdder和AtomicLong执行效率
下面我们做个小实验,通过代码来观察一下 LongAdder和AtomicLong执行的一个效率,我们使用10个线程,每个线程累计累加1000次管家一下执行时间
2.1.AtmoictLong
private static final Logger log = LoggerFactory.getLogger(LongAdderTest.class);
@Test
@DisplayName("测试AtmoictLong的执行效率")
public void testLongAdder() {
long start = System.currentTimeMillis();
CountDownLatch latch = new CountDownLatch(10);
AtomicLong longAdder = new AtomicLong();
ArrayList<Thread> threads = new ArrayList<>();
for (int j = 0; j <10; j++) {
threads.add(new Thread(()->{
for (int i = 0; i < 1000000000; i++) {
longAdder.incrementAndGet();
}
latch.countDown();
}));
}
// 启动全部线程
threads.forEach(Thread::start);
// 等待全部线程执行完毕
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
long end = System.currentTimeMillis();
log.error("执行结果:{}",longAdder.longValue());
log.error("执行耗时:{}ms",end-start);
}