比AtomicLong更高效的并发计数器

标签: atomiclong 并发 计数器 | 发表时间:2014-04-20 17:29 | 作者:
出处:http://it.deepinmind.com

我喜欢新鲜玩意儿,而Java 8里面就有 不少。这回我准备介绍一下我的一个最爱——并发计数器。这是一组新的类,用于维护多个线程并发读写的计数器。新的API带来了显著的性能提升,同时还保证了接口的简单易用。

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

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

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

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

synchronzied——这是最基础的同步操作了,只要你在读写值,它就会阻塞住其它的所有线程。这种方式的确行得通,不过肯定的是,你的程序运行起来会像 DMV排的长队那样。

读写锁(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都有可能产生脏值。

测试的代码在 这里

结论

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

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

译注:想深入了解LongAdders的工作原理的话,可以读下并发编程网上的 这篇文章

原创文章转载请注明出处: 比AtomicLong更高效的并发计数器

英文原文链接

相关 [atomiclong 并发 计数器] 推荐:

比AtomicLong更高效的并发计数器

- - Java译站
我喜欢新鲜玩意儿,而Java 8里面就有 不少. 这回我准备介绍一下我的一个最爱——并发计数器. 这是一组新的类,用于维护多个线程并发读写的计数器. 新的API带来了显著的性能提升,同时还保证了接口的简单易用. 多核时代来临了之后,大家都开始使用并发计数器,我们先来看一下Java迄今为止提供了哪些实现方式,它们的性能和这个新的API相比,又有什么不同.

高并发

- - 开源软件 - ITeye博客
垂直扩展是一种用于增加单个ActiveMQ代理连接数(因而也增加了负载能力)的技术.默认情况下,. ActiveMQ的被设计成尽可高效的传输消息以确保低延迟和良好的性能. 默认情况下,ActiveMQ使用阻塞IO来处理传输连接,这种方式为每一个连接分配一个线程. 你可以为ActiveMQ代理使用非阻塞IO(同时客户端可以使用默认的传输)以减少线程的使用.

并发导论

- - 并发编程网 - ifeve.com
由于之前工作中的疏忽,在使用Java多线程并发的时候出了问题,遂决心全面学习并发相关知识. 写作本文的意图只是希望在写作过程中把想不清楚或是一时无法掌握的地方反复揣摩记录下来. 写作本文参考的各种资料较多,抱歉的是文末的参考文献中对一些叫不上名字或没有出处的资料文献并未列举出来. 由于本人是初入职场的菜鸟,更是并发的门外汉,文中关于并发以及其他软硬件、程序设计语言的论据也许不够客观甚至不够正确.

J.U.C并发框架

- - 并发编程网 - ifeve.com
作者:Doug Lea. 在J2SE1.5中,java.util.concurrent包下的大部分同步工具(锁、屏障等)以AbstractQueuedSynchronizer类为基础来构建. 这个框架提供了一些常用机制用于自动管理并发状态、阻塞及非阻塞线程,以及队列. 本论文描述了该框架的根源、设计、实现、用法及性能.

高并发---限流

- - Java - 编程语言 - ITeye博客
在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流. 缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹;而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开;而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流.

Redis在计数器场景上的应用

- - searchdatabase
  对于计数器大家肯定还有或多或少的疑问.   通常我们发布的社交内容会存储在数据库中,最常见的如MySQL:.   更新索引:insert into user_message(uid,messageid) values(‘xx’,’xx’).   更新内容:insert into message_2013_05(messageid,message) values(‘xx’,'xx’);.

江南Style点击率突破YouTube计数器上限

- - Solidot
YouTube的Google+账号透露,韩国歌手鸟叔的《江南Style》太受欢迎了,其点击率竟打破了YouTube的计数器上限. 《江南Style》的浏览量超过了21亿次,打破了32位整数计数器的上限(2,147,483,647),使得YouTube不得不将计数器升级到64位整数. 升级之后,YouTube的计数器能记录到最高9,223,372,036,854,775,808次浏览量,在可预见的未来应该没人能打破了.

用redis实现社交产品中计数器 - jockchou

- - 博客园_首页
社交产品业务里有很多统计计数的功能,比如:. 用户: 总点赞数,关注数,粉丝数. 帖子: 点赞数,评论数,热度. 消息: 已读,未读,红点消息数. 话题: 阅读数,帖子数,收藏数. 写的性能对MySQL是一个挑战. 可以采用redis来优化高频率写入的性能要求. 对于每一个实体的计数,设计一个hash结构的counter:.

通过计数器实现接口限流

- - 行业应用 - ITeye博客
如果接口可能会有突发访问情况,但又担心访问量大而导致系统崩溃,这个时候就需要对接口的请求并发数进行限制了. 可以为相应的接口设置对应的阈值,超过则拒绝请求或者排队等待. 1、使用java自带的concurrent包下的类进行限流,示例代码如下:. 2、使用guava的Cache来限流,示例代码如下:.

tomcat 高并发优化

- - 企业架构 - ITeye博客
maxThreads:tomcat起动的最大线程数,即同时处理的任务个数,默认值为200. minSpareThreads 表示空闲的线程数,据我的理解,类似于连接池. acceptCount:当tomcat起动的线程数达到最大时,接受排队的请求个数,默认值为100.    另外,有时候,还需要调整jvm的启动参数.