背景:并发知识是一个程序员段位升级的体现,同样也是进入BAT的必经之路,有必要把并发知识重新梳理一遍。

ConcurrentHashMap:
在有了并发的基础知识以后,再来研究concurrent包。普通的HashMap为非线程安全的,在高并发场景下要使用线程安全版本的ConcurrentHashMap;

众所周知HashTable可以保证线程安全但却效率低下,而HashMap是非线程安全但效率却高于HashTable,于是ConcurrentHashMap就孕育而生成为二者的结合体,为了更好的理解ConcurrentHashMap先看下这两个Map。

HashMap

HashMap之所以具有很快的访问速度,因为它是根据键的hashCode值来存储数据,在大多数情况下可以直接定位到它的值,但遍历的顺序是不确定的;HashMap的key可以为null,但是最多只允许一条记录的键为null,另外允许多条记录(value)的值为null,key为null的键值对永远都放在一table[0]为头节点的链表中;HashMap为非线程安全的,适用于单线程环境下,即在任一时刻都可以有多个线程同时对HashMap进行读或写操作,可以会导致数据的不一致;如果一定要使用HashMap又要保证线程安全,则可以用Collection的synchronizedMap方法或ConcurrentHashMap都OK;HashMap是基于哈希实表实现的,每一个元素是一个key-value对,其内部通过单链表结局冲突问题的,当Map容量不足(超过了阀值)时链表会自动增长;HashMap实现了Serializable接口,因此其支持序列化,并且实现了Cloneable接口,可以被克隆;

HashMap存储数据的过程:

HashMap内部维护了一个存储数据的Entry数组,HashMap采用链表解决冲突,每一个Entry本质上其实是一个单向链表;当要添加一个key-value对时,首先会通过hash(key)方法技术hash值,然后通过indexFor(hash,length)求该key-value对的存储位置,其计算方法是先用Hash&0x7FFFFFFF后,再对length取模,这就保证了每一个key-value对都能存入HashMap,当计算出相同的位置是,由于存入位置是一个链表,所以把这个key-value对插入链表头。

如上图1 所示,最左边竖列排的多个方格就代表哈希表,也叫哈希数组,数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中;HashMap内存储数据的Entry数组默认是16,如果没有对Entry扩容机制的话,当存储的数据一多,Entry内部的链表会很长,这就失去了HashMap的存储意义了,所以HashMap内部有自己的扩容机制(当size大于threshold时,对HashMap进行扩容)。 上图2 是HashMap的链表存储结构,其中E*代表一个Node节点,每个Node节点就对应着一个key-value的mapping映射;每个Node除了保存了key和value的映射之外,还保存了它下一Node的引用(Eb保存了Ebb的引用,而Ebb保存了Ebbb的引用);图2中,每一个链表如Ec-->Ecc-->Eccc,这三个节点的key是不相等的。

分析HashMap源码会发现其内部有几个重要的变量如:size用于记录HashMap的底层数组中已用槽的数量、threshold用于HashMap的阈值判断,看是否需要调整HashMap的容量(threshold = 容量*加载因子)、DEFAULT_LOAD_FACTOR = 0.75f,即加载因子默认0.75。 HashMap的扩容是是新建了一个HashMap的底层数组,通过调用transfer方法,将就HashMap的全部元素添加到新的HashMap中(此步需要重新计算元素在新的数组中的索引位置,导致HashMap扩容成为一个相当耗时的操作),So我们在用HashMap的时,最好能提前预估下HashMap中元素的个数,这样有助于提高HashMap的性能。

HashTable
HashTable的功能与HashMap类似,如同样是基于哈希表实现的、内部也是通过单链表解决冲突问题、容量不足时也会自动增加、同样实现了Seriablizable接口支持序列化、实现了Cloneable接口可克隆;不同的是HashTable继承自Dictionary类且为线程安全的(任一时间只有一个线程可以写HashTable,但性能不如

ConcurrentHashMap),而HashMap继承AbstractMap类且非线程安全。

如图3,HashTable只有一把锁,当一个线程访问HashTable的同步方法时,会将整张table 锁住,当其他线程也想访问HashTable 同步方法时,就会进入阻塞或轮询状态。也就是确保同一时间只有一个线程对同步方法的占用,避免多个线程同时对数据的修改,由此确保线程的安全性;但HashTable 对get,put,remove 方法都使用了同步操作,这就造成如果两个线程都只想使用get 方法去读取数据时,因为一个线程先到进行了锁操作,另一个线程就不得不等待,这样必然导致效率低下,而且竞争越激烈,效率越低下。

ConcurrentHashMap(并发且线程安全)

ConcourrentHashMap是通过分段锁技术来保证线程安全的[case:一个人到酒店开房可直接在前台办理入住,三个陌生人到酒店开房登记入住,另外两个则要先排队等第一个办理结束(普通的Map),要是三个人所住的每个楼层都有一个可以办理入住的前台就无需排队了(ConcurrentHashMap)];ConcurrentHashMap主要由Segment(桶)和HashEntry(节点)两大数据组成,如下图:

在hashMap 的基础上,ConcurrentHashMap将数据分为多个segment(默认16个),然后每次操作对一个segment 加锁,HashTable 在竞争激烈的并发环境下表现出效率低下的原因是由于所有访问HashTable的线程都必须竞争同一把锁,而ConcurrentHashMap将数据分到多个segment 中(默认16,也可在申明时自己设置,不过一旦设定就不能更改,扩容都是扩充各个segment 的容量),由于每个segment 都有一个自己的锁,只要多个线程访问的不是同一个segment 就没有锁争用,就没有堵塞,也就是允许16个线程并发的更新并且不存在锁争用现象。除此之外,ConcurrentHashMap的segment就类似一个HashTable,但比HashTable又更进一步优化,因为HashTable对get,put,remove方法都会使用锁,而ConcurrnetHashMap中get方法是不涉及到锁的;并且ConcurrentHashMap内部在并发读取时,除了key 对应的value为null的情况下会用到锁,其它的场景下都没有用到锁,所以对于读操作无论多少线程并发都是安全高效的。

并发concurrent---3的更多相关文章

  1. Java并发---concurrent包

    一.包的结构层次 其中包含了两个子包atomic和locks,另外字concurrent下的阻塞队列以及executor,这些就是concurrent包中的精华.而这些类的实现主要是依赖于volati ...

  2. 并行parallel和并发concurrent的区别

    http://stackoverflow.com/questions/1050222/concurrency-vs-parallelism-what-is-the-difference Concurr ...

  3. java 并发 concurrent Executor

    Excutor类 Executor 执行提交的对象Runnable任务. ExecutorService 一个Executor ,提供方法来管理终端和方法,可以产生Future为跟踪一个或多个异步任务 ...

  4. Java虚拟机6:内存溢出和内存泄露、并行和并发、Minor GC和Full GC、Client模式和Server模式的区别

    前言 之前的文章尤其是讲解GC的时候提到了很多的概念,比如内存溢出和内存泄露.并行与并发.Client模式和Server模式.Minor GC和Full GC,本文详细讲解下这些概念的区别. 内存溢出 ...

  5. iOS 处理多个网络请求的并发的情况

    如何处理多个网络请求的并发的情况 一.概念 1.并发 当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配 ...

  6. 并发concurrent---2

    背景:并发知识是一个程序员段位升级的体现,同样也是进入BAT的必经之路,有必要把并发知识重新梳理一遍. 并发concurrent: 使用ThreadLocal可以实现线程范围内共享变量,线程A写入的值 ...

  7. 并发concurrent---1

    背景:并发知识是一个程序员段位升级的体现,同样也是进入BAT的必经之路,有必要把并发知识重新梳理一遍. 并发concurrent: 说到并发concurrent,肯定首先想到了线程,创建线程有两种方法 ...

  8. python多进程并发和多线程并发和协程

    为什么需要并发编程? 如果程序中包含I/O操作,程序会有很高的延迟,CPU会处于等待状态,这样会浪费系统资源,浪费时间 1.Python的并发编程分为多进程并发和多线程并发 多进程并发:运行多个独立的 ...

  9. java并发编程概念

    并发:当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其 ...

  10. 《C#并发编程经典实例》学习笔记-关于并发编程的几个误解

    误解一:并发就是多线程 实际上多线程只是并发编程的一种形式,在C#中还有很多更实用.更方便的并发编程技术,包括异步编程.并行编程.TPL 数据流.响应式编程等. 误解二:只有大型服务器程序才需要考虑并 ...

随机推荐

  1. UWP中实现大爆炸效果(二)

    上一回实现了一个宽度不均匀的Panel,这次我们编写一个简单的BigbangView主体. 首先创建一个模板化控件,删掉Themes/Generic.xaml中的<Style TargetTyp ...

  2. 基于SpringBoot从零构建博客网站 - 技术选型和整合开发环境

    技术选型和整合开发环境 1.技术选型 博客网站是基于SpringBoot整合其它模块而开发的,那么每个模块选择的技术如下: SpringBoot版本选择目前较新的2.1.1.RELEASE版本 持久化 ...

  3. 从0到1:使用Caliburn.Micro(WPF和MVVM)开发简单的计算器

    从0到1:使用Caliburn.Micro(WPF和MVVM)开发简单的计算器 之前时间一直在使用Caliburn.Micro这种应用了MVVM模式的WPF框架做开发,是时候总结一下了. Calibu ...

  4. 用Docker解决坑爹的环境搭建系列——mysql:5.6

    sudo docker pull mysql:5.6 mkdir -p /data/docker/mysql/data /data/docker/mysql/logs /data/docker/mys ...

  5. Cron表达式详解【转】

    Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式: Seconds Minutes Hours DayofMonth Month ...

  6. DateTime Tips

    DateTime Tips(System.Runtime Version=4.2.1.0) 抛砖引玉,如有错误或是更好的方式还望不吝赐教 1. 根据某个DateTime对象取其当天的起始时间例如:输入 ...

  7. laravel中如何利用反射实现依赖注入

    依赖注入 在一个类中经常会依赖于其他的对象,先看一下经典的写法 class Foo { public $bar; public function __construct() { $this->b ...

  8. UiPath如何实现暂停功能?

    照理说一个无人值守的机器人原本是不应该有人工操作介入的,也就不会提供暂停功能.但客户可能出于业务需要,或者风险管控的考虑,会需要机器人具备暂停功能.通常,会希望在机器人运行时,用户摁下快捷键,机器人就 ...

  9. 关于mui前端传值,springboot后台接收值的问题

    最近做app,使用mui的ajax给后台传参,后台一直接收不到值,表示很蛋疼.这里通过网上搜索加上个人实践,总结归纳了三种前端传值和后台接收的方式. 第一种: 前端: data: JSON.strin ...

  10. Beanstalkd工作队列

    Beanstalkd工作队列Beanstalkd 是什么Beanstalkd是目前一个绝对可靠,易于安装的消息传递服务,主要用例是管理不同部分和工人之间的工作流应用程序的部署通过工作队列和消息堆栈,类 ...