ConcurrentHashMap是HashMap的多线程版本。HashMap在并发操作的时候会出现各种问题,比如死循环问题,数据覆盖等问题。而这些问题都可以通过使用ConcurrentHashMap来完美解决。问题来了,ConcurrentHashMap是如何保证线程安全的呢?它的底层是如何实现的?接下来,我们一起来看看吧。JDK1.7ConcurrentHashMap的底层实现在不同的JDK版本中实现方式不同。**在JDK1.7中,以数组加链表的形式实现,数组分为:大数组Segment和小数组HashEntry。**大数组Segment可以理解为MySQL中的一个数据库,每个数据库(Segment)中有很多表HashEntry,每个HashEntry中有多条数据,这些数据通过链表连接起来,如图下图:JDK1.7线程安全的实现了解了ConcurrentHashMap的底层实现之后,再看它的线程安全实现就比较简单了。接下来我们看看JDK1.7中的ConcurrentHashMap是如何通过增加element的put方法来保证线程安全的。具体实现源码如下:finalVput(Kkey,inthash,Vvalue,booleanonlyIfAbsent){//在Segment写入之前,确保获取到锁HashEntrynode=tryLock()?null:scanAndLockForPut(key,hash,value);V旧值;try{//段内部数组HashEntry[]tab=table;intindex=(tab.length-1)&hash;HashEntry首先=entryAt(tab,index);for(HashEntrye=first;;){if(e!=null){Kk;//更新已有值...}else{//将HashEntry放在特定位置,超过阈值则重新哈希//忽略其他代码...}}}finally{//释放锁unlock();}returnoldValue;}从上面的源码可以看出,Segment本身是基于ReentrantLock来实现加锁和释放操作的,这样可以保证多个线程同时访问ConcurrentHashMap时,只有一个Thread可以操作相应的节点,从而保证ConcurrentHashMap的线程安全。也就是说ConcurrentHashMap的线程安全是基于Segmentlocking的,所以我们称之为segmentlock或segmentlock,如下图所示:JDK1.8的底层是在JDK1.7实现的。ConcurrentHashMap虽然是线程安全的,但是由于其底层实现是数组+链表的形式,在数据量大的时候访问非常慢,因为需要遍历整个链表,而JDK1.8使用了一个数组+链表/红黑树的方式优化了ConcurrentHashMap的实现。具体实现结构如下:链表升级为红黑树的规则:当链表长度大于8,且数组长度大于64时,链表为升级为红黑树结构。PS:虽然ConcurrentHashMap保留了JDK1.8中Segment的定义,但这只是为了保证序列化时的兼容性,不再有任何结构上的用途。JDK1.8线程安全实现在JDK1.8中,ConcurrentHashMap使用CAS+volatile或者synchronized来保证线程安全。其核心实现源码如下:finalVputVal(Kkey,Vvalue,booleanonlyIfAbsent){if(key==null||value==null)thrownewNullPointerException();inthash=spread(key.hashCode());intbinCount=0;for(Node[]tab=table;;){Nodef;诠释n,我,fh;Kfk;Vf;如果(tab==null||(n=tab.length)==0)tab=initTable();elseif((f=tabAt(tab,i=(n-1)&hash))==null){//节点为空//使用CAS执行无锁线程安全操作,ifbin为空if(casTabAt(tab,i,null,newNode(hash,key,value)))中断;}elseif((fh=f.hash)==MOVED)tab=helpTransfer(tab,f);elseif(onlyIfAbsent&&fh==hash&&((fk=f.key)==key||(fk!=null&&key.equals(fk)))&&(fv=f.val)!=null)返回fv;else{VoldVal=null;synchronized(f){//细粒度同步修改操作...}}//如果超过阈值,则升级为红黑树if(binCount!=0){if(binCount>=TREEIFY_THRESHOLD)treeifyBin(tab,我);如果(oldVal!=null)返回oldVal;休息;}}}addCount(1L,binCount);returnnull;}从上面的源码可以看出,在JDK1.8中,添加元素时,首先会判断容器是否为空。如果为空,则使用volatile加CAS进行初始化。如果容器不为空,则根据存储的元素计算该位置是否为空。如果为空,则使用CAS进行设置。节点;如果不为空,则使用synchronize加锁,遍历桶中的数据,更换或添加节点到桶中,最后判断是否需要转换成红黑树,保证并发时的线程安全使用权。让我们简化上面的过程。我们可以简单的认为,在JDK1.8中,ConcurrentHashMap是在头节点加锁的,以保证线程安全。锁的粒度比Segment更小,减少了冲突和加锁的频率,提高了并发操作的性能。而且JDK1.8使用了红黑树来优化之前的固定链表,所以在数据量比较大的时候,查询性能也有了很大的提升,从之前的O(n)优化时间到O(logn)time复杂度,具体加锁示意图如下:总结ConcurrentHashMap在JDK1.7中是以数据加链表的形式实现的。数组分为两大类:大数组Segment和小数组HashEntry,在Segment上加了锁ReentrantLock,使用锁来实现线程安全。在JDK1.8中,ConcurrentHashMap是使用数组+链表/红黑树的方式实现的。通过CAS或者synchronized实现线程安全,锁粒度更小,查询性能也更高。