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,一经查实,立即删除!

    相关文章

    HDU 5652 India and China Origins(二分 + BFS)

    本文链接:http://www.cnblogs.com/Ash-ly/p/5398867.html 题意: 中国和印度之间有一片地方,把这片地方抽象化,于是就可以看成一个N * M矩阵,其中黑色的代表高山不能走过去,白色的代表平原,可以通行,人每次可…

    C语言%.2f四舍五入

    #include <stdio.h> int main() {double d 1.199;printf("%.2f", d);return 0; }输出1.20 如果不想让其四舍五入可以这样&#xff1a; #include <stdio.h> #include <math.h> int main() {double d 1.199;printf("%.2f", floor(d * 1…

    关于使用racthet的push.js

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

    休眠自动提交命令强制MySQL在过多的磁盘I / O中运行

    亲爱的大家&#xff0c; 我敢肯定&#xff0c;你们中的许多人都在使用Hibernate和MySQL&#xff0c;我自己在这里和那里都使用它。 通常&#xff0c;编程模型是不错的&#xff0c;但是普通的JDBC可以快很多已经不是什么秘密了。 在这篇文章中&#xff0c;我想引起您的注意Hibe…

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

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

    Linux: dev: cmake: CHECK_LIBRARY_EXISTS

    文章目录 简介例子源代码最终调用到的两个命令如果结果是这里为什么不直接使用rpm查看包呢&#xff1f;需要注意的问题 简介 https://cmake.org/cmake/help/latest/module/CheckLibraryExists.html 这个方法是在Modules/CheckLibraryExists.cmake文件里定义的一个宏。 最终使用…

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

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

    Struts2的全局结果视图的配置

    1.在struts.xml中的package标签内添加<global-results/>标签&#xff0c;将全局结果加进该标签内&#xff0c;只适用于当前包下。 <package name"customer" namespace"/customer" extends"struts-default" > <global-results>…

    长大了Java! 提出Java桌面版

    不&#xff0c;这不是另一个“ Java已死”的咆哮。 Java非常活跃。 它是可用的最佳开发和运行时平台之一。 迄今为止&#xff0c;最稳定的平台。 那可能只是它最大的祸根。 荒谬&#xff01; 稳定性如何&#xff1f; 你可能会问。 好吧&#xff0c;由于它&#xff0c;您可以看到…

    [算法练习]Excel Sheet Column Title

    题目&#xff1a; Given a positive integer, return its corresponding column title as appear in an Excel sheet. For example: 1 -> A 2 -> B 3 -> C ... 26 -> Z 27 -> AA 28 -> AB 代码&#xff1a; class Solution { public: string convertToTitle(…

    7-16 求符合给定条件的整数集 (15 分)

    给定不超过6的正整数A&#xff0c;考虑从A开始的连续4个数字。请输出所有由它们组成的无重复数字的3位数。 输入格式&#xff1a; 输入在一行中给出A。 输出格式&#xff1a; 输出满足条件的的3位数&#xff0c;要求从小到大&#xff0c;每行6个整数。整数间以空格分隔&#…

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

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

    Java最佳实践– Vector vs ArrayList vs HashSet

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

    iOS:图片上传时两种图片压缩方式的比较

    上传图片不全面的想法&#xff1a;把图片保存到本地,然后把图片的路径上传到服务器&#xff0c;最后又由服务器把路径返回&#xff0c;这种方式不具有扩展性&#xff0c;如果用户换了手机&#xff0c;那么新手机的沙盒中就没有服务器返回的图片路径了&#xff0c;此时就无法获取…

    7-17 爬动的蠕虫 (15 分)

    一条蠕虫长1寸&#xff0c;在一口深为N寸的井的底部。已知蠕虫每1分钟可以向上爬U寸&#xff0c;但必须休息1分钟才能接着往上爬。在休息的过程中&#xff0c;蠕虫又下滑了D寸。就这样&#xff0c;上爬和下滑重复进行。请问&#xff0c;蠕虫需要多长时间才能爬出井&#xff1f;…

    浅谈泛型之泛型方法

    实际不用多说只举2个例子就行: //例1 static void fromArrayToCollection(Object[] a, Collection<?> c) {for (Object o : a) { c.add(o); // 编译错误,错误原因也很简单,<?>是无上下界的通配符泛型,所以编译器根本无法确认类型} } //例2 static <T> void…

    Android特效 五种Toast详解

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

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

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

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

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

    java只使用try和finally不使用catch的原因和场景

    JDK并发工具包中&#xff0c;很多异常处理都使用了如下的结构&#xff0c;如AbstractExecutorService&#xff0c;即只有try和finally没有catch。 class X {private final ReentrantLock lock new ReentrantLock();// ...public void m(){lock.lock(); // block until condi…