比AtomicLong更高效的并发计数器

2024-01-26 06:08

本文主要是介绍比AtomicLong更高效的并发计数器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我喜欢新鲜玩意儿,而Java 8里面就有[url=http://www.javacodegeeks.com/2014/03/5-features-in-java-8-that-will-change-how-you-code.html]不少[/url]。这回我准备介绍一下我的一个最爱——并发计数器。这是一组新的类,用于维护多个线程并发读写的计数器。新的API带来了显著的性能提升,同时还保证了接口的简单易用。

多核时代来临了之后,大家都开始使用并发计数器,我们先来看一下Java迄今为止提供了哪些实现方式,它们的性能和这个新的API相比,又有什么不同。

脏计数器——选择这种方式意味着多线程直接并发读写一个普通对象或者静态字段。不幸的是,这么做是行不通的。有两个原因,一个是在Java里, A+= B操作不是原子的。如果你打开编译后的字节码看一下,你会发现至少有四条指令——第一条是从堆里将字段值加载到线程栈里,第二条是加载要增加的值,第三条指令将它们进行相加,第四条则将结果写回到字段中。

如果多个线程同时在同一个内存位置进行这个操作,你的一个写操作很有可能就丢掉了,因为另一个线程可能会覆盖了它的值。还有一个很恶心的事就是这个值的可见性。下面还会详细介绍到。

新手非常容易犯这样的错误,而这样的问题却很难发现。如果你发现团队中有人这么做,最好能帮我个小忙。在你的数据库里面搜一下我的名字“Tal Weiss"。如果你发现我在里面——赶紧把我的记录删掉。这样我会感觉舒服点。

synchronzied——这是最基础的同步操作了,只要你在读写值,它就会阻塞住其它的所有线程。这种方式的确行得通,不过肯定的是,你的程序运行起来会像[url=https://www.youtube.com/watch?v=1w_XVPImsqQ]DMV排的长队[/url]那样。

读写锁(RWLock)——这个和基础的Java锁相比就巧妙了些,它可以让你区分出那些要修改值因此需要阻塞别人的进程以及那些只是读取值不需要进入临界区的。虽然这个方法有的时候很高效(比如写线程的数量比较少的话),但还是相当无语,因为当你获取写锁的时候还是会阻塞住其它线程的执行。

volatile——这个经常会被误用的关键字会让JIT编译停止在运行时进行机器码的优化工作,因此字段一旦有更新别的线程马上就能看到。

它会使得JIT编译器经常玩的一些把戏比如说调整赋值语句的顺序这些无法进行。JIT编译器有可能会改变字段的赋值顺序。什么,你再说一遍?是的,你听的没错。这个神秘的小把戏使得它可以减小程序访问全局堆的次数,同时它还能保证不会影响到你的程序的执行。这真是有点偷偷摸摸的感觉。

那什么时候应该使用volatile计数器?如果你只有一个线程在更新一个值,而多个线程在读的话,这是个很合适的场景。因为完全没有竞争。

你可能会问为什么都使用它就完了?因为如果有多个线程在更新的话就会有问题了。由于A+=B不是一个原子操作,这么做的话可能会覆盖掉别人写的话。在Java 8以前,这种情况你就只能用AtomicInteger了。

AtomicInteger——这组类使用了处理器的CAS (compare-and-swap)指令来更新计数器的值。听起来不错吧?一半一半吧。由于它直接使用机器指令来设置值,因此对其它线程的影响最小。不好的一面是如果它和别的线程有竞争赋值失败了,它会继续重试。在高并发的条件 下,这就成了一个自旋锁,线程会在一个无限的循环内不断的尝试赋值,直到成功为止。我们可不太想看到这种局面。Java 8来了,还带来了LongAdders。

Java 8 Adders——这是个非常棒的新的API,我对它的仰慕有如滔滔江水连绵不绝。从使用者的角度来说,它很像AtomicInteger。只需要创建一个LongAdder对象,然后使用intValue()以及add()方法来获取和设置它的值。而奇迹就发生在这一切的背后。

如果由于竞争这个类的CAS操作失败了的话,它会要添加的值存到一个线程本地的内部的cell对象里。当intValue()方法调用 的时候,它把这些cell的值加到总和里。这样就减少了CAS重试或者阻塞别的线程的情况。真不错的想法。

说的也差不多了。我们来看看它的真本事。我们做了如下的一个基准测试:把一个计数器设置为0,然后多个线程开始读取并进行自增。当计数器到达10^8的时候停止。我们在一个4核的i7处理器上运行这个测试。

我用了10个线程来运行这个基准测试——读写分别使用5个线程来进行,这样的话会出现严重的竞争条件:

注意:脏读和volatile都有可能产生脏值。

[img]http://a3ab771892fd198a96736e50.javacodegeeks.netdna-cdn.com/wp-content/uploads/2014/04/table_april-16_02-2.jpg[/img]

测试的代码在[url=https://github.com/takipi/counters-benchmark]这里[/url]。

[b]结论[/b]

[*]并发的Adder类和AtomicInteger相比有60~100%的性能提升。
[*]增加线程不会对结果有太大影响,除非是使用锁的情况。
[*]注意到如果使用synchronized或者读写锁,性能会有很大的损耗——慢了一个数量级!

如果你已经在代码里使用到它了——我会感到非常高兴。

译注:想深入了解LongAdders的工作原理的话,可以读下并发编程网上的[url=http://ifeve.com/atomiclong-and-longadder/]这篇文章[/url]。

原创文章转载请注明出处:[url=http://it.deepinmind.com]http://it.deepinmind.com[/url]

[url=http://www.takipiblog.com/2014/04/16/java-8-longadders-the-fastest-way-to-add-numbers-concurrently/]英文原文链接[/url]

这篇关于比AtomicLong更高效的并发计数器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/645862

相关文章

Java高效实现PowerPoint转PDF的示例详解

《Java高效实现PowerPoint转PDF的示例详解》在日常开发或办公场景中,经常需要将PowerPoint演示文稿(PPT/PPTX)转换为PDF,本文将介绍从基础转换到高级设置的多种用法,大家... 目录为什么要将 PowerPoint 转换为 PDF安装 Spire.Presentation fo

Java JUC并发集合详解之线程安全容器完全攻略

《JavaJUC并发集合详解之线程安全容器完全攻略》Java通过java.util.concurrent(JUC)包提供了一整套线程安全的并发容器,它们不仅是简单的同步包装,更是基于精妙并发算法构建... 目录一、为什么需要JUC并发集合?二、核心并发集合分类与详解三、选型指南:如何选择合适的并发容器?在多

Java 结构化并发Structured Concurrency实践举例

《Java结构化并发StructuredConcurrency实践举例》Java21结构化并发通过作用域和任务句柄统一管理并发生命周期,解决线程泄漏与任务追踪问题,提升代码安全性和可观测性,其核心... 目录一、结构化并发的核心概念与设计目标二、结构化并发的核心组件(一)作用域(Scopes)(二)任务句柄

C#使用Spire.Doc for .NET实现HTML转Word的高效方案

《C#使用Spire.Docfor.NET实现HTML转Word的高效方案》在Web开发中,HTML内容的生成与处理是高频需求,然而,当用户需要将HTML页面或动态生成的HTML字符串转换为Wor... 目录引言一、html转Word的典型场景与挑战二、用 Spire.Doc 实现 HTML 转 Word1

Web服务器-Nginx-高并发问题

《Web服务器-Nginx-高并发问题》Nginx通过事件驱动、I/O多路复用和异步非阻塞技术高效处理高并发,结合动静分离和限流策略,提升性能与稳定性... 目录前言一、架构1. 原生多进程架构2. 事件驱动模型3. IO多路复用4. 异步非阻塞 I/O5. Nginx高并发配置实战二、动静分离1. 职责2

Redis实现高效内存管理的示例代码

《Redis实现高效内存管理的示例代码》Redis内存管理是其核心功能之一,为了高效地利用内存,Redis采用了多种技术和策略,如优化的数据结构、内存分配策略、内存回收、数据压缩等,下面就来详细的介绍... 目录1. 内存分配策略jemalloc 的使用2. 数据压缩和编码ziplist示例代码3. 优化的

使用SpringBoot+InfluxDB实现高效数据存储与查询

《使用SpringBoot+InfluxDB实现高效数据存储与查询》InfluxDB是一个开源的时间序列数据库,特别适合处理带有时间戳的监控数据、指标数据等,下面详细介绍如何在SpringBoot项目... 目录1、项目介绍2、 InfluxDB 介绍3、Spring Boot 配置 InfluxDB4、I

C#高效实现Word文档内容查找与替换的6种方法

《C#高效实现Word文档内容查找与替换的6种方法》在日常文档处理工作中,尤其是面对大型Word文档时,手动查找、替换文本往往既耗时又容易出错,本文整理了C#查找与替换Word内容的6种方法,大家可以... 目录环境准备方法一:查找文本并替换为新文本方法二:使用正则表达式查找并替换文本方法三:将文本替换为图

Python如何实现高效的文件/目录比较

《Python如何实现高效的文件/目录比较》在系统维护、数据同步或版本控制场景中,我们经常需要比较两个目录的差异,本文将分享一下如何用Python实现高效的文件/目录比较,并灵活处理排除规则,希望对大... 目录案例一:基础目录比较与排除实现案例二:高性能大文件比较案例三:跨平台路径处理案例四:可视化差异报

Spring Security 前后端分离场景下的会话并发管理

《SpringSecurity前后端分离场景下的会话并发管理》本文介绍了在前后端分离架构下实现SpringSecurity会话并发管理的问题,传统Web开发中只需简单配置sessionManage... 目录背景分析传统 web 开发中的 sessionManagement 入口ConcurrentSess