为并发吞吐性能所做的优化

ConcurrentHashMap使用了一些技巧来获取高的并发性能,同时避免了锁。这些技巧包括:

  • 为不同的Hash bucket(所谓hash bucket即不同范围的key的hash值)使用多个写锁;
  • 利用JMM(Java Memory Model,java内存模型)的不确定性使得持有锁的时间最小化,或者从根本上避免使用锁。

ConcurrentHashMap为最常用的场景进行了优化,比如获取一个已经存在于Map中的值。事实上,绝大多数成功的get()操作在运行中根本就没有使用锁,这是因为利用了JMM的不确定性。

多个写锁

回忆一下HashTable的线程安全是因为使用了一个单独的全部Map范围的锁,这个锁在所有的插入、删除、查询操作中都会持有,甚至在使用
Iterator遍历整个Map时也会持有这个单独的锁。当锁被一个线程持有时,就能够防止其他线程访问该Map,即便其他线程都处于闲置状态。这种单个
锁的机制极大的限制了并发的性能。

而ConcurrentHashMap抛弃了仅仅使用整个Map范围的一个锁的机制,取而代之的是使用了32个锁,每个锁会负责hash
bucket的一个子集(即负责一部分key的hash值范围)。而且这些锁仅仅被那些更改Map内容的操作使用,比如put(),
remove()。拥有32个单独的锁意味时最多可以有32个线程同时修改Map,这并不是说如果有少于32个线程同时修改Map而没有线程被阻塞,32
仅仅是理论上的并发写操作的极限,在实际中一般不一定会达到。在目前的软硬件条件下,对多数程序而言,32个锁总比一个锁要好。

Map范围的锁

32个独立的锁,每个锁负责hash
bucket的一个子集,这意味着如果有些排他的访问操作,就需要获得全部的32个锁,比如rehashing(即扩充hash
bucket的数量,当HashMap的key增长时重新分布key元素)就必须是排他的访问。但是JAVA语言本身没有提供一种简单的方式来获取变长的
锁,由于这种操作不会频繁发生,那么ConcurrentHashMap是使用递归实现Map范围的锁。

JMM(Java内存模型)概览

JMM就是Java内存模型,它定义了Java的线程之间如何通过内存进行交互。简单说就是: JVM中存在一个主内存(Main Memory
or Java Heap Memory),JAVA中所有变量都储存在主存中,对于所有线程都是共享的。每个线程都有自己的工作内存(Working

Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主
存完成。工作内存里的变量, 在多核CPU的情况下, 将大部分储存于处理器高速缓存中, 高速缓存在不经过内存时,
所以线程之间也是不可见的。详细的关于JMM的介绍会在后面的文章中。

大家都知道,CPU尽量使用自己的寄存器内部的数据,这样会极大的提高性能。JAVA规范(JLS-Java Language
Specification)中规定了一些内存操作不必立即被其他线程发现,并且还提供了两个语言层面的机制来确保在多个线程之间保持内存操作的一致
性:synchronized和volatile。根据JSL中的描述"In the absence of explicit
synchronization, an implementation is free to update the main memory in
an order that may be surprising."
(在没有显示的同步时,一个操作可以以一种令人惊讶的方式自由的更新主存。)这意思是说:没有同步时,在一个线程中写操作的操作顺序也许和另外一个线程的
写操作顺序不一样,并且更新内存变量会在不确定的时间之后被其他线程发现。

而使用synchronized的最根本的原因就是确保线程访问关键代码段的原子性。synchronized实际上提供了三个功能
atomicity, visibility, and
ordering(原子性,可见性和顺序行)。所谓原子性很好理解也很直接,就是确保不同线程再次进入同一区域的互斥性,防止在同一时刻有多于一个线程可
以访问到被保护的代码段。很不幸的是许多的文章只强调了synchronized的原子性方面,而不说其他两个方面。在JMM中,同步扮演了一个重要的角
色,当获取和释放monitor(锁)的时候,同步操作使得JVM执行了内存屏障(execute memory barriers)。

当一个线程获取一个锁时,它便执行了一次内存读屏障(read
barriers)——所谓内存屏障就是使得任何其他线程缓存的本地内存(CPU内部缓存或者CPU寄存器)无效,然后使得其他所有线程所在的CPU重新
从主存(内存)读取这些变量的值。同样,在释放锁的时候,所有其他线程均执行了一次内存写屏障(write
barriers)——Flush任何已经更改的变量到主存(内存)。互斥和内存屏障的结合意味着只要程序遵循正确的同步规则(这个同步规则就是:同步任
何被写的变量会下次被另一个线程正确的读;或者是同步任何读一个下一次会被另一个线程正确的更改变量),每个线程都会看到它所使用的共享变量的正确值。

如果在访问共享变量时没有同步,你会碰到一些奇怪的事情,一些改变会很快的反应到其他线程里,而其他一些更改则需要花费一些时间才能反应到其他线
程中。这样的结果就会使得你如果不用synchronized,那么你不能确保你看到一致的内存视图(即相关变量在不同的线程中的值会不一致,也许有一些
值是脏数据)。通常的方法,也是推荐的方法去避免这些脏数据当然是正确采用synchronized。在这种情况下,比如在广泛使用的基础类
ConcurrentHashMap中就值得使用一些额外的专门知识和功夫去开发,以获得高性能。

ConcurrentHashMap是如何提高并发时的吞吐性能的更多相关文章

  1. java ConcurrentHashMap和CopyOnWriteArrayList解决并发问题

    ConcurrentHashMap 一.hashtable.hashmap.ConcurrentHashMap 1.线程不安全的HashMap 因为多线程环境下,使用Hashmap进行put操作会引起 ...

  2. SQL Server数据库读写分离提高并发性

    在一些大型的网站或者应用中,单台的SQL Server 服务器可能难以支撑非常大的访问压力.很多人在这时候,第一个想到的就是一个解决性能问题的利器——负载均衡.遗憾的是,SQL Server 的所有版 ...

  3. STM 软件事务内存——本质是为提高并发,通过事务来管理内存的读写访问以避免锁的使用

    对Java程序员来说,我们对面向对象的编程(OOP)自然都是烂熟于胸的,但语言也极大地影响了我们构建面向对象应用程序的方式.(现在的OOP已经和Alan Kay当初创造这个词时候的初衷大不相同了,他的 ...

  4. ecshop 秒杀并发时库存会被减到小于0的解决办法

    ecshop 秒杀并发时库存会被减到小于0更新库存后,再进行库存检查,如果库存为负数,则执行事务的回滚. begin();//开始一个事物处理开始 $sql = "UPDATE " ...

  5. 关于sphinx+PHP在高并发时响应性能低下的解决办法

    经过多次压力测试,发现sphinx在高并发时出现负载突然提升,并且响应速度明显下降.经过多方面的排查,发现是由于PHP与sphinx自带的 searchd进行socket的连接之后,系统内存有大量的T ...

  6. FluorineFx 播放FLV 时堆棧溢出解决 FluorineFx NetStream.play 并发时,无法全部连接成功的解决办法

    http://25swf.blogbus.com/tag/FluorineFx/ http://www.doc88.com/p-7002019966618.html  基于Red5的视频监控系统的研究 ...

  7. [转]你如何面对—LNMP高并发时502

    From : http://www.topthink.com/topic/5683.html 之前php-fpm配置: 单个php-fpm实例,使用socket方式,内存8G 静态方式,启动php-f ...

  8. Linux的虚拟内存管理-如何分配和释放内存,以提高服务器在高并发情况下的性能,从而降低了系统的负载

    Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全 ...

  9. j2ee高并发时使用全局变量需要注意的问题

    原文:https://blog.csdn.net/jston_learn/article/details/21617311 开发中,全局变量的使用很频繁,但对于多线程的访问,使用全局变量需要注意的地方 ...

随机推荐

  1. [javascript svg fill stroke stroke-width rx ry ellipse 属性讲解] svg fill stroke stroke-width ellipse 绘制椭圆属性讲解

    <!DOCTYPE html> <html lang='zh-cn'> <head> <title>Insert you title</title ...

  2. Sharepoint学习笔记—习题系列--70-576习题解析 -(Q81-Q83)

    Question  81You are designing a custom administrative timer job for a SharePoint 2010 farm. You need ...

  3. UIWebView的应用和其中的JS与OC间传值

    现在有很多的应用已经采用了WebView和html语言结合的开发模式.html5一直很火因为一份代码可以在多个平台上运用啊,效果各不相同都很美观,也越来越有一些公司直接招后台程序员和html5程序员, ...

  4. 获取在线APP的素材图片

    1.打开iTunes,搜索并下载APP 2.打开下载的APP的路径 4.对ipa包进行解压 5.找到app,右键"显示包内容"进行查看 6.结果

  5. jQuery导航切换功能

    Css ------------------------------------------------------------------------------------------------ ...

  6. Android的进程等级

    Android五个进程等级 1.前台进程(Foreground process): 用户当前工作所需要的.一个进程如果满足下列任何条件被认为是前台进程: 正运行着一个正在与用户交互的活动(Activi ...

  7. Android自定义控件1

    概述 Android已经为我们提供了大量的View供我们使用,但是可能有时候这些组件不能满足我们的需求,这时候就需要自定义控件了.自定义控件对于初学者总是感觉是一种复杂的技术.因为里面涉及到的知识点会 ...

  8. 【代码笔记】iOS-手机号验证

    代码: - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. // ...

  9. OC NSSet

    OC NSSet 数组的特点: 有序的 Set的特点: 无序的,存储元素无重复(例:set中有两个元素'a' 输出时只输出一个a) NSSet初始化 NSSet * set = [[NSSet all ...

  10. [转]Json转换神器之Google Gson的使用

    这几天,因为项目的需要,接触了Google的Gson库,发现这个东西很好用,遂记下简单的笔记,供以后参考.至于Gson是干什么的,有什么优点,请各位同学自行百度.话不多说,切入正题: 1. 下载Gso ...