Java最佳实践–队列之战和链接的ConcurrentHashMap

在使用Java编程语言时,我们将继续讨论与建议的实践有关的系列文章,我们将在四个具有相关语义的流行Queue实现类之间进行性能比较。 为了使事情变得更现实,我们将在多线程环境下进行测试,以讨论和演示如何将ArrayBlockingQueue , ConcurrentLinkedQueue , LinkedBlockingQueue和/或LinkedList用于高性能应用程序。

最后但并非最不重要的一点是,我们将提供自己的ConcurrentHashMap实现。 从ConcurrentLinkedHashMap实施不同的ConcurrentHashMap ,它维持于所有条目的运行双向链表。 此链表定义了迭代顺序,通常是将键插入映射中的顺序。 从组合的特点ConcurrentLinkedHashMap利益的ConcurrentHashMap和LinkedHashMap的实现业绩略低于该的ConcurrentHashMap的 ,由于增加了维护链接列表的额外费用。

所有讨论的主题均基于用例,这些用例来自于电信行业的关键任务超高性能生产系统的开发。

在阅读本文的每个部分之前,强烈建议您参考相关的Java API文档以获取详细信息和代码示例。

所有测试均针对具有以下特征的Sony Vaio进行:

  • 系统:openSUSE 11.1(x86_64)
  • 处理器(CPU):Intel(R)Core(TM)2 Duo CPU T6670 @ 2.20GHz
  • 处理器速度:1,200.00 MHz
  • 总内存(RAM):2.8 GB
  • Java:OpenJDK 1.6.0_0 64位

应用以下测试配置:

  • 并发工作线程数:50
  • 每个工作人员测试重复次数:100
  • 整体测试次数:100

ArrayBlockingQueue与ConcurrentLinkedQueue与LinkedBlockingQueue与LinkedList

Java开发人员必须执行的最常见任务之一是从Collections中存储和检索对象。 Java编程语言提供了一些具有重叠和独特特征的Collection实现类。 从Java 1.5开始, Queue实现类已成为在处理之前保存元素的事实上的标准。 除了基本的“ 收集”操作外,队列还提供其他插入,提取和检查操作。

但是,使用Queue实现类,尤其是在多线程环境中,可能会很棘手。 默认情况下,它们中的大多数提供并发访问,但是可以以阻塞或非阻塞方式来处理并发。 BlockingQueue实现类支持以下操作:在检索元素时等待队列变为非空,并在存储元素时等待队列中的空间变为可用。

这里将要讨论的案例场景是通过ConcurrentLinkedQueue和LinkedList Queue实现类以及ArrayBlockingQueue和LinkedBlockingQueue BlockingQueue实现类的元素来插入,缩回和迭代多个线程 。 我们将演示如何在多线程环境中正确利用上述集合实现类,并提供相关的性能比较表,以显示在每个测试用例中哪个性能更好。

为了进行票价比较,我们将假定不允许使用NULL元素,并且在适用的情况下限制每个队列的大小。 因此,我们的测试组的BlockingQueue实现类将被初始化,其最大大小为5000个元素–请记住,我们将使用50个工作线程来执行100次测试重复。 此外,由于LinkedList是我们测试组中唯一不默认提供并发访问的Queue实现类,因此将使用同步块访问列表来实现LinkedList的并发性。 在这一点上,我们必须明确指出LinkedList Collection实现类的Java文档建议使用Collections.synchronizedList静态方法来维护对列表的并发访问。 此方法提供指定Collection实现类的“包装”同步实例,如下所示:

列表syncList = Collections.synchronizedList(new LinkedList());

当您要将特定的实现类用作List而不是Queue时,此方法是合适的。 为了能够使用特定Collection实现类的“队列”功能,必须按原样使用它,或将其强制转换为Queue接口。

测试案例1 –在队列中添加元素

对于第一个测试用例,我们将有多个线程在每个Queue实现类中添加String元素。 为了保持String元素之间的唯一性,我们将如下所示构造它们:

  • 静态的第一部分,例如“ helloWorld”
  • 工作线程ID,请记住,我们有50个并发运行的工作线程
  • worker线程测试重复次数,请记住,每个worker线程每次测试执行100次测试重复

对于每个测试运行,每个工作线程将插入100个String元素,如下所示:

  • 对于第一次测试重复
    • 工作线程1将插入String元素:“ helloWorld-1-1”
    • 工作线程2将插入String元素:“ helloWorld-2-1”
    • 工作线程3将插入String元素:“ helloWorld-3-1”
    • 等等...
  • 对于第二次测试重复
    • 工作线程1将插入String元素:“ helloWorld-1-2”
    • 工作线程2将插入String元素:“ helloWorld-2-2”
    • 工作线程3将插入String元素:“ helloWorld-3-2”
    • 等等...
  • 等等...

在每次测试运行结束时,每个Queue实现类都将填充5000个不同的String元素。 为了添加元素,我们将使用BlockingQueue实现类的“ put()”操作和Queue实现类的“ offer()”操作,如下所示:

  • arrayBlockingQueue.put(“ helloWorld-” + id +“-” + count);
  • linkedBlockingQueue.put(“ helloWorld-” + id +“-” + count);
  • parallelLinkedQueue.offer(“ helloWorld-” + id +“-” + count);
  • 已同步(linkedList){
    linkedList.offer(“ helloWorld-” + id +“-” + count);
    }

下面我们提供了上述四个Queue实现类之间的性能比较表

横轴表示测试运行的次数,纵轴表示每次测试运行的每秒平均事务数(TPS)。 因此,较高的值更好。 如您所见,向其添加元素时,所有Queue实现类的执行几乎相同。 与LinkedList和LinkedBlockingQueue相比, ArrayBlockingQueue和ConcurrentLinkedQueue的性能稍好。 后者表现最差,平均得分为625000 TPS。

测试案例2 –从队列中删除元素

对于第二个测试用例,我们将有多个线程从每个Queue实现类中删除String元素。 所有队列实现类都将使用来自先前测试用例的String元素进行预填充。 每个线程将从每个Queue实现类中删除单个元素,直到Queue为空。

为了删除元素,我们将对BlockingQueue实现类使用“ take()”操作,对Queue实现类使用“ poll()”操作,如下所示:

  • arrayBlockingQueue.take();
  • linkedBlockingQueue.take();
  • concurrentLinkedQueue.poll();
  • 已同步(linkedList){
    linkedList.poll();
    }

下面我们提供了上述四个Queue实现类之间的性能比较表

横轴表示测试运行的次数,纵轴表示每次测试运行的每秒平均事务数(TPS)。 因此,较高的值更好。 同样,从队列中删除String元素时,所有Queue实现类的性能几乎相同。 与LinkedList和LinkedBlockingQueue相比, ArrayBlockingQueue和ConcurrentLinkedQueue的性能稍好。 后者是最差的性能,平均得分为710000 TPS。

测试案例#3 –迭代器

对于第三个测试用例,我们将有多个工作线程在每个Queue实现类的元素上进行迭代。 每个工作线程将使用Queue的“ iterator()”操作检索对Iterator实例的引用,并使用Iterator的“ next()”操作遍历所有可用的Queue元素。 所有Queue实现类都将使用第一个测试用例的String值预先填充。 下面是上述测试用例的性能比较表。

横轴表示测试运行的次数,纵轴表示每次测试运行的每秒平均事务数(TPS)。 因此,较高的值更好。 与ConcurrentLinkedQueue和LinkedList实现类相比, ArrayBlockingQueue和LinkedBlockingQueue实现类的性能均较差。 LinkedBlockingQueue平均得分为35 TPS,而ArrayBlockingQueue平均得分为81 TPS。 另一方面, LinkedList的性能优于ConcurrentLinkedQueue ,平均导致15000 TPS。

测试案例4 –添加和删除元素

对于我们的最终测试用例,我们将实现测试用例1和测试用例2场景的组合。 换句话说,我们将实现生产者-消费者测试用例。 一组辅助线程将向每个Queue实现类插入String元素,而另一组辅助线程将从其中撤回String元素。 每一个主题从“插入元素”组将会只有一个元素插入,而每一次的主题从“缩回元素”组是要收回一个元素。 因此,我们将从每个Queue实现类中同时插入和撤回5000个唯一的String元素。

要正确模拟上述测试情况下,我们必须启动所有工作线程是收回之前的元素开始工作者线程是插入的元素。 为了使“收回元素”组的工作线程能够收回单个元素,如果相关队列为空,它们必须等待并重试。 默认情况下, BlockingQueue实现类提供等待功能,而Queue实现类则不提供。 因此,为了删除元素,我们将对BlockingQueue实现类使用“ take()”操作,对Queue实现类使用“ poll()”操作,如下所示:

  • arrayBlockingQueue.take();
  • linkedBlockingQueue.take();
  • while(结果== null)
    结果= parallelLinkedQueue.poll();
  • while(结果== null)
    已同步(linkedList){
    结果= linkedList.poll(); }

    如您所见,我们实现了最低限度的while循环,以使ConcurrentLinkedQueue和LinkedList使用者能够在从空Queue撤回时执行重新连接。 当然,您可以尝试并实施更复杂的方法。 尽管如此,请记住,上述内容以及任何其他人工实现方法都不是建议的解决方案,应避免使用BlockingQueue “ take()”操作,如以下性能比较表所示。

    下面是上述测试用例的添加部分的性能比较表。

    以下是上述测试案例的收回部分的性能比较表。

    横轴表示测试运行的次数,纵轴表示每次测试运行的每秒平均事务数(TPS)。 因此,较高的值更好。 不出所料, ArrayBlockingQueue和LinkedBlockingQueue的表现均优于LinkedList和ConcurrentLinkedQueue 。 BlockingQueue实现被设计为主要用于生产者-消费者队列。 在这种情况下,特别是当生产者线程和使用者线程的数量相对较高时,它们的阻塞行为使它们具有无与伦比的性能和效率。 实际上,根据相关的性能比较,随着生产者线程和使用者线程数量的增加, Queue实现类和BlockingQueue实现类之间的性能相对增益会有所提高,而后者则有所提高。

    从提供的性能结果中可以看出, LinkedBlockingQueue获得了最佳的组合(添加和删除元素)性能结果,并且应该是实施生产者(消费者)方案的第一候选人。

    ConcurrentLinkedHashMap

    我们的ConcurrentLinkedHashMap实现是最初由Doug Lea编码并在OpenJDK 1.6.0_0上发现的ConcurrentHashMap实现的调整版本。 我们提出了ConcurrentMap接口的并发哈希图和链表实现,并具有可预测的迭代顺序。 此实现与ConcurrentHashMap的不同之处在于,它维护一个遍历其所有条目的双向链接列表。 此链表定义了迭代顺序,通常是将键插入映射的顺序(插入顺序)。 请注意,如果将密钥重新插入到映射中,则插入顺序不会受到影响。

    此实现使客户摆脱了ConcurrentHashMap和Hashtable提供的未指定的,通常混乱的排序,而不会增加与TreeMap相关的成本。 无论原始地图的实现如何,都可以使用它来生成与原始地图具有相同顺序的地图副本:

    void foo(Map m){
    地图副本=新的ConcurrentLinkedHashMap(m);
    … }

    如果模块在输入上获取映射,将其复制并随后返回结果(其顺序由副本的顺序确定),则此技术特别有用。 (客户通常喜欢按退货的顺序退货。)

    提供了一个特殊的“ ConcurrentLinkedHashMap(int,float,int,boolean)”构造函数,以创建并发链接的哈希映射,该哈希映射的迭代顺序是其条目的最后访问顺序,即从最近访问到最近访问(访问-订购)。 这种映射非常适合构建LRU缓存。 调用put或get方法将导致对相应条目的访问(假设调用完成后该条目存在)。 “ putAll()”方法为指定映射中的每个映射生成一个条目访问,其顺序为指定映射的条目集迭代器提供键-值映射。 没有其他方法可以生成条目访问。 特别是,对集合视图的操作不会影响支持映射的迭代顺序。

    可以重写“ removeEldestEntry(Map.Entry)”方法,以强加一个策略,以便在将新映射添加到地图时自动删除陈旧的映射。

    由于维护链接列表会增加开销,因此性能可能会略低于ComcurrentHashMap,但有一个例外:对ConcurrentLinkedHashMap的集合视图进行迭代需要的时间与地图的大小成正比,而无论其容量如何。 在ConcurrentHashMap上进行迭代可能会更昂贵,所需的时间与其容量成正比。

    您可以从此处下载最新版本的ConcurrentLinkedHashMap源代码和二进制代码

    您可以从此处 , 此处和此处下载用于进行性能比较的所有“测试器”类的源代码。

    快乐编码

    贾斯汀

    相关文章 :

    • Java最佳实践–多线程环境中的DateFormat
    • Java最佳实践–高性能序列化
    • Java最佳实践– Vector vs ArrayList vs HashSet
    • Java最佳实践–字符串性能和精确字符串匹配
    • Java最佳实践– Char到Byte和Byte到Char的转换

    翻译自: https://www.javacodegeeks.com/2010/09/java-best-practices-queue-battle-and.html

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/374838.shtml

    如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

    相关文章

    关于使用racthet的push.js

    racthet的push是用来跳转另外一个页面的效果的。但是必须在服务器的环境下支持。如果想要让本地html访问支持的话需要添加 转载于:https://www.cnblogs.com/djawh/p/4623925.html

    “应用程序无法正常启动(oxc000007b)”解决方案

    解决方案1 通过“DirectX修复工具 V3.3 标准版”软件修复。 备注:经过测试,并未解决本人的问题,但是这个方法可能对游戏中缺失相关.dll(动态链接库)有帮助。 解决方案2: 该问题的出现不适偶然,主…

    7-15 计算圆周率 (15 分)

    根据下面关系式,求圆周率的值,直到最后一项的值小于给定阈值。 输入格式: 输入在一行中给出小于1的阈值。 输出格式: 在一行中输出满足阈值条件的近似圆周率,输出到小数点后6位。 输入样例: 0.01结尾无…

    JUnit学习之hamcrest、testSuite介绍及测试原则

    [转自] http://huihai.iteye.com/blog/1994270 上一节说了junit的一些基本概念,主要使用assert做一些基本的判断。但很多时候使用assert做判断,并不方便,如果要判断某几个值是否为true或false,这时使用hamcrest来判断就会方便许多…

    Java最佳实践– Vector vs ArrayList vs HashSet

    在使用Java编程语言时,我们将继续讨论与建议的实践有关的系列文章,我们将在三个最常用的Collection实现类之间进行性能比较。 为了使事情变得更现实,我们将在多线程环境下进行测试,以讨论和演示如何将Vector , ArrayLi…

    Android特效 五种Toast详解

    Toast是Android中用来显示显示信息的一种机制,和Dialog不一样的是,Toast是没有焦点的,而且Toast显示的时间有限,过一定的时间就会自动消失。而且Toast主要用于向用户显示提示消息,接下来巴士为大家总结了Android五种To…

    Java最佳实践–高性能序列化

    在使用Java编程语言时,我们将继续讨论与建议的实践有关的系列文章,我们将讨论并演示如何将对象序列化用于高性能应用程序。 所有讨论的主题均基于用例,这些用例来自于电信行业的关键任务超高性能生产系统的开发。 在阅读本文的每个部分之前…

    7-18 二分法求多项式单根 (20 分)

    二分法求函数根的原理为&#xff1a;如果连续函数f(x)在区间[a,b]的两个端点取值异号&#xff0c;即f(a)f(b)<0&#xff0c;则它在这个区间内至少存在1个根r&#xff0c;即f0。 二分法的步骤为&#xff1a; 检查区间长度&#xff0c;如果小于给定阈值&#xff0c;则停止&a…

    UITableViewCell 选中的状态小技巧

    - (void)setSelected:(BOOL)selected animated:(BOOL)animated {[super setSelected:selected animated:animated]; //cell 没被选中时 隐藏这个 _leftImageViewself.leftImageView.hidden !selected; //选中text变红 不然变灰色self.textLabel.textColor selected ? [UICol…

    BZOJ 3143 HNOI2013 游走 高斯消元 期望

    这道题是我第一次使用高斯消元解决期望类的问题&#xff0c;首发A了&#xff0c;感觉爽爽的.... 不过笔者在做完后发现了一些问题&#xff0c;在原文的后面进行了说明。 中文题目&#xff0c;就不翻大意了&#xff0c;直接给原题&#xff1a; 一个无向连通图&#xff0c;顶点从…

    eclipse启动tomcat, http://localhost:8080无法访问的解决方案

    问题:&#xff1a; tomcat在eclipse里面能正常启动&#xff0c;但在浏览器中访问http://localhost:8080/不能访问tomcat管理页面&#xff0c;且报404错误。同时其他项目页面也不能访问。访问的时候出现下列页面: 现在关闭eclipse里面的tomcat&#xff0c;在tomcat安装目录下双击…

    洛谷P1014 [NOIP1999 普及组] Cantor 表

    现代数学的著名证明之一是 Georg Cantor 证明了有理数是可枚举的。他是用下面这一张表来证明这一命题的&#xff1a; 代码 import java.util.*; public class Main{public static void main(String[] args){//int x1 0;int i 0;Scanner sc new Scanner(System.in);int n s…

    3522: [Poi2014]Hotel( 树形dp )

    枚举中点x( 即选出的三个点 a , b , c 满足 dist( x , a ) dist( x , b ) dist( x , c ) ) , 然后以 x 为 root 做 dfs , 显然两个位于 x 的同一颗子树内的点是不可能被同时选到的 . 我们对 x 的每一颗子树进行 dfs , 记录下当前子树中的点到 x 距离为 d ( 1 < d < n )…

    洛谷P1035 [NOIP2002 普及组] 级数求和

    代码 import java.util.Scanner;public class Main {public static void main(String args[]){Scanner sc new Scanner(System.in);int k sc.nextInt();int n 0;double Sn 0;while(Sn<k){n;Sn Sn 1.0/n;}System.out.println(n);} }这样写while循环体这需要每次加上1/…

    Java中的Google ClientLogin实用程序

    Google API的身份验证和授权是当今需要与Google服务集成和信息交换的应用程序中的常见功能。 尽管大多数Google身份验证过程是针对Web应用程序量身定制的&#xff0c;但它也可用于桌面和已安装的应用程序。 对于桌面应用程序&#xff0c;Google建议使用称为ClientLogin的身份验…

    ViewPager使用笔记

    1.ViewPager.setCurrentItem(position)&#xff0c;即使已设置动画&#xff0c;但是没有动画效果 原因&#xff1a;因为ViewPager滑动之前的时间间隔太短&#xff0c;可以通过反射&#xff0c;去修改ViewPager自动滑动时间&#xff0c;代码实现如下 1 public class ViewPagerSc…

    iOS 8 Xcode6 设置Launch Image 启动图片

    本人apem http://www.mamicode.com/info-detail-494411.html 如何设置App的启动图,也就是Launch Image? Step1 1.点击Image.xcassets 进入图片管理,然后右击,弹出"New Launch Image"2.如图,右侧的勾选可以让你选择是否要对ipad,横屏,竖屏,以及低版本的ios系统做支持…

    Apache CXF负载平衡和故障转移

    前一段时间&#xff0c;我们已经面临基于Apache CXF的负载平衡Web服务客户端的需求。 此外&#xff0c;当某些服务器关闭时&#xff0c;客户端应自动进行故障转移。 更糟糕的是&#xff0c;服务器目标地址列表要从外部服务获取并在运行时更新。 最终&#xff0c;我们最终获得了…

    PAT-BASIC-1038-统计同成绩学生

    本题要求读入N名学生的成绩&#xff0c;将获得某一给定分数的学生人数输出。 输入格式&#xff1a; 输入在第1行给出不超过105的正整数N&#xff0c;即学生总人数。随后1行给出N名学生的百分制整数成绩&#xff0c;中间以空格分隔。最后1行给出要查询的分数个数K&#xff08;不…

    JavaScript事件处理的三种方式(转)

    一、什么是JavaScript事件&#xff1f;事件(Event)是JavaScript应用跳动的心脏&#xff0c;也是把所有东西粘在一起的胶水&#xff0c;当我们与浏览器中Web页面进行某些类型的交互时&#xff0c;事件就发生了。 事件可能是用户在某些内容上的点击、鼠标经过某个特定元素或按下键…