并发concurrent---3

背景:并发知识是一个程序员段位升级的体现,一样也是进入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接口,能够被克隆;app

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,但性能不如
spa

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的状况下会用到锁,其它的场景下都没有用到锁,因此对于读操做不管多少线程并发都是安全高效的。
相关文章
相关标签/搜索