1.å¦ä½å¨javaä¸ä½¿ç¨ConcurrentHashMap
2.Java乐观锁的源码实现原理和典型案例
3.Java高并发编程实战7,ConcurrentHashMap详解
4.concurrenthashmap1.8源码如何详细解析?源码
5.震惊!ConcurrentHashMap里面也有死循环,源码作者留下的源码“彩蛋”了解一下?
6.HashMapãHashTableãConcurrentHashMapçåçä¸åºå«
å¦ä½å¨javaä¸ä½¿ç¨ConcurrentHashMap
åèå¦ä¸å 容:
ConcurrentHashMapéçæ¹å¼æ¯ç¨å¾®ç»ç²åº¦çã ConcurrentHashMapå°hash表å为个桶ï¼é»è®¤å¼ï¼ï¼è¯¸å¦get,put,removeç常ç¨æä½åªéå½åéè¦ç¨å°ç桶ã
è¯æ³ï¼åæ¥ åªè½ä¸ä¸ªçº¿ç¨è¿å ¥ï¼ç°å¨å´è½åæ¶ä¸ªå线ç¨è¿å ¥ï¼å线ç¨æéè¦éå®ï¼è读线ç¨å ä¹ä¸åéå¶ï¼ä¹åä¼æå°ï¼ï¼å¹¶åæ§çæåæ¯æ¾èæè§çã
æ´ä»¤äººæ讶çæ¯ConcurrentHashMapç读å并åï¼å 为å¨è¯»åç大å¤æ°æ¶åé½æ²¡æç¨å°éå®ï¼æ以读åæä½å ä¹æ¯å®å ¨ç并åæä½ï¼èåæä½éå®çç²åº¦åé常ç»ï¼æ¯èµ·ä¹ååæ´å å¿«éï¼è¿ä¸ç¹å¨æ¡¶æ´å¤æ¶è¡¨ç°å¾æ´ææ¾äºï¼ãåªæå¨æ±sizeçæä½æ¶æéè¦éå®æ´ä¸ªè¡¨ã
èå¨è¿ä»£æ¶ï¼ConcurrentHashMap使ç¨äºä¸åäºä¼ ç»éåçå¿«é失败è¿ä»£å¨çå¦ä¸ç§è¿ä»£æ¹å¼ï¼æ们称为弱ä¸è´è¿ä»£å¨ãå¨è¿ç§è¿ä»£æ¹å¼ä¸ï¼å½iterator被å建åéåååçæ¹åå°±ä¸åæ¯æåº ConcurrentModificationExceptionï¼åè代ä¹çæ¯å¨æ¹åæ¶newæ°çæ°æ®ä»èä¸å½±ååæçæ° æ®ï¼iteratorå®æååå°å¤´æéæ¿æ¢ä¸ºæ°çæ°æ®ï¼è¿æ ·iterator线ç¨å¯ä»¥ä½¿ç¨åæ¥èçæ°æ®ï¼èå线ç¨ä¹å¯ä»¥å¹¶åçå®ææ¹åï¼æ´éè¦çï¼è¿ä¿è¯äºå¤ä¸ªçº¿ç¨å¹¶åæ§è¡çè¿ç»æ§åæ©å±æ§ï¼æ¯æ§è½æåçå ³é®ã
ä¸é¢åæConcurrentHashMapçæºç ã主è¦æ¯åæå ¶ä¸çSegmentãå 为æä½åºæ¬ä¸é½æ¯å¨Segmentä¸çãå çSegmentå é¨æ°æ®çå®ä¹ã
Java乐观锁的实现原理和典型案例
乐观锁在并发编程中的实现主要有两种方式:CAS(Compare and Swap)和版本号控制。CAS是源码实现乐观锁的核心算法,它通过比较内存中的源码晃晃大联盟源码值是否和预期的值相等来判断是否存在冲突。Java中提供了AtomicInteger、源码AtomicLong、源码AtomicReference等原子类来支持CAS操作。源码版本号控制则是源码乐观锁的另一种实现方式,每当一个线程要修改数据时,源码都会先读取当前的源码版本号或时间戳,并将其保存下来。源码线程完成修改后,源码会再次读取当前的源码版本号或时间戳,如果发现已经变化,则说明有其他线程对数据进行了修改,此时需要回滚并重试。
Java中乐观锁的案例包括ConcurrentHashMap和LongAdder。ConcurrentHashMap使用了分离锁(Segment)来保证线程安全,并使用了基于CAS的技术来实现乐观锁。LongAdder在高并发场景下提供了比AtomicInteger和AtomicLong更好的性能,代价是消耗更多的内存空间。乐观锁在数据库并发控制中最常见的应用是使用版本号进行更新操作,确保操作的图文卡片源码原子性。
总结来说,在Java中并发编程时,可以使用Synchronized和Lock实现的同步锁机制,但它们属于悲观锁,在高并发场景下可能会导致线程阻塞和性能开销。乐观锁,如CAS和版本号控制,可以提供更高的并发性,但CAS只能保证单个变量操作的原子性,对于多个变量的场景则无能为力。乐观锁在数据库更新操作中有着广泛的应用,通过使用版本号确保操作的一致性和原子性。
Java高并发编程实战7,ConcurrentHashMap详解
在Java高并发编程中,ConcurrentHashMap是一个重要的数据结构,它在不同版本中有着显著的优化。早期的HashMap使用数组+链表结构,遇到哈希冲突时会形成链表,而JDK1.7的ConcurrentHashMap引入了分段锁(segment),每个segment都是一个HashEntry数组,降低了加锁粒度,提高了并发性能。在JDK1.8中,ConcurrentHashMap进一步改进,抖音地铁源码采用数组+链表/红黑树的形式,直接使用volatile避免数据冲突,并利用synchronized和CAS算法确保线程安全。
CopyOnWrite策略利用冗余实现读写分离,避免了锁竞争。操作流程是:读操作在原容器进行,写操作在新容器,写完后指向新容器,旧容器被回收。这样既支持高并发读,又能保证写操作的线程安全。
另一方面,BlockingQueue作为线程安全的队列,提供了丰富的操作方法。常见的方法包括但不限于入队、出队、查看队列大小等,它是并发编程中处理任务调度和同步的重要工具,支持阻塞和非阻塞操作,适合处理生产者-消费者模型。
concurrenthashmap1.8源码如何详细解析?
ConcurrentHashMap在JDK1.8的线程安全机制基于CAS+synchronized实现,而非早期版本的分段锁。
在JDK1.7版本中,ConcurrentHashMap采用分段锁机制,货运健康码源码包含一个Segment数组,每个Segment继承自ReentrantLock,并包含HashEntry数组,每个HashEntry相当于链表节点,用于存储key、value。默认支持个线程并发,每个Segment独立,互不影响。
对于put流程,与普通HashMap相似,首先定位至特定的Segment,然后使用ReentrantLock进行操作,后续过程与HashMap基本相同。
get流程简单,通过hash值定位至segment,再遍历链表找到对应元素。需要注意的是,value是volatile的,因此get操作无需加锁。
在JDK1.8版本中,线程安全的关键在于优化了put流程。首先计算hash值,遍历node数组。水费查询系统源码若位置为空,则通过CAS+自旋方式初始化。
若数组位置为空,尝试使用CAS自旋写入数据;若hash值为MOVED,表示需执行扩容操作;若满足上述条件均不成立,则使用synchronized块写入数据,同时判断链表或转换为红黑树进行插入。链表操作与HashMap相同,链表长度超过8时转换为红黑树。
get查询流程与HashMap基本一致,通过key计算位置,若table对应位置的key相同则返回结果;如为红黑树结构,则按照红黑树规则获取;否则遍历链表获取数据。
震惊!ConcurrentHashMap里面也有死循环,作者留下的“彩蛋”了解一下?
在探讨一个近期发现的JDK 8的BUG时,我们了解到Dubbo 2.7.7版本更新点的描述中,涉及到了与JDK相关的修复。这个BUG实际上是位于闻名的concurrent包中的computeIfAbsent方法。在JDK 9中被修复,修复者正是Doug Lea。由于ConcurrentHashMap就是Doug Lea的杰作,这个BUG可以被视作“谁污染谁治理”。为了理解这个BUG的成因,需要先了解computeIfAbsent方法的用途。
computeIfAbsent方法的目的是当Map中某个key对应的值不存在时,通过调用mappingFunction函数并返回该函数执行结果(非null)作为key的值。如初始化一个ConcurrentHashMap,第一次获取名为why的value,若未获取到,则返回null。接着调用computeIfAbsent方法获取null后,调用getValue方法,将返回值与当前key关联起来。因此,第二次获取时能拿到"why技术"。
了解了方法的用途,接下来揭示这个BUG。通过链接,我们看到具体的描述指向了concurrent包中的computeIfAbsent方法。这个BUG在JDK 9中被修复,修复人正是Doug Lea。要理解BUG,需要先了解这个方法的工作流程。在JDK 8之后,computeIfAbsent方法提供了第二个参数mappingFunction,该方法的含义是当前Map中key对应的值不存在时,调用mappingFunction函数,并将该函数的执行结果(非null)作为该key的value返回。
通过一系列代码演示,我们发现正常情况下,Map应该显示{ AaAa=,BBBB=},但在JDK 8环境中运行给定的测试用例时,方法不会结束,而是陷入了死循环。这就是BUG。
在这个BUG被发现的过程中,提问的艺术也发挥了重要作用。Doug Lea和Pardeep在讨论中展示了提问和回答的策略。Pardeep提供的测试案例是转折点,它清晰地指出了问题所在。Doug Lea在问题提出后不久给出了回复,提出了解决问题的可能性,并且在后续的讨论中逐步明确了这个BUG的存在以及可能的解决方法。最终,这个BUG在JDK 9中得到了修复。
这个BUG的成因在于computeIfAbsent方法内部的另一个computeIfAbsent调用,导致了两个方法在处理相同的key时进入了死循环。在处理此BUG时,我们需要深入理解computeIfAbsent方法的工作流程。从代码分析中可以看出,当key为"AaAa"和"BBBB"时,它们在进行计算和存储操作时,由于key的哈希值相同,导致了循环条件无法满足break的情况,从而进入了死循环。
总结这个BUG,我们发现当key的哈希值相同时,多次调用computeIfAbsent方法会导致死循环。Doug Lea在JDK 9中通过增加判断条件,避免了这种循环情况,从而修复了这个BUG。对于使用JDK 8的开发者,可以通过将computeIfAbsent方法的使用方式调整为先调用get方法,再使用putIfAbsent方法,以此避免遇到这个BUG。此外,我们还提到了线程安全问题,即虽然ConcurrentHashMap本身是线程安全的,但在使用时仍需注意避免线程冲突,以确保程序的正确性。
HashMapãHashTableãConcurrentHashMapçåçä¸åºå«
ä»ç±»å¾ä¸å¯ä»¥çåºæ¥å¨åå¨ç»æä¸ConcurrentHashMapæ¯HashMapå¤åºäºä¸ä¸ªç±»SegmentãConcurrentHashMapæ¯ç±Segmentæ°ç»ç»æåHashEntryæ°ç»ç»æç»æãSegmentæ¯ä¸ä¸ªå¯éå ¥éï¼ReentrantLockï¼ï¼å¨ConcurrentHashMapéæ®æ¼éçè§è²ï¼HashEntryåç¨äºåå¨é®å¼å¯¹æ°æ®ãä¸ä¸ªConcurrentHashMapéå å«ä¸ä¸ªSegmentæ°ç»ãSegmentçç»æåHashMap类似ï¼æ¯ä¸ç§æ°ç»åé¾è¡¨ç»æãä¸ä¸ªSegmentéå å«ä¸ä¸ªHashEntryæ°ç»ï¼æ¯ä¸ªHashEntryæ¯ä¸ä¸ªé¾è¡¨ç»æçå ç´ ï¼æ¯ä¸ªSegmentå®æ¤çä¸ä¸ªHashEntryæ°ç»éçå ç´ ãå½å¯¹HashEntryæ°ç»çæ°æ®è¿è¡ä¿®æ¹æ¶ï¼å¿ é¡»é¦å è·å¾ä¸å®å¯¹åºçsegmentéã
ConcurrentHashMapæ¯ä½¿ç¨äºéå段ææ¯æ¥ä¿è¯çº¿ç¨å®å ¨çã
éå段ææ¯ï¼é¦å å°æ°æ®åæä¸æ®µä¸æ®µçåå¨ï¼ç¶åç»æ¯ä¸æ®µæ°æ®é ä¸æéï¼å½ä¸ä¸ªçº¿ç¨å ç¨é访é®å ¶ä¸ä¸ä¸ªæ®µæ°æ®çæ¶åï¼å ¶ä»æ®µçæ°æ®ä¹è½è¢«å ¶ä»çº¿ç¨è®¿é®ã
ConcurrentHashMapæä¾äºä¸HashtableåSynchronizedMapä¸åçéæºå¶ãHashtableä¸éç¨çéæºå¶æ¯ä¸æ¬¡éä½æ´ä¸ªhash表ï¼ä»èå¨åä¸æ¶å»åªè½ç±ä¸ä¸ªçº¿ç¨å¯¹å ¶è¿è¡æä½ï¼èConcurrentHashMapä¸åæ¯ä¸æ¬¡éä½ä¸ä¸ªæ¡¶ã
Hashtable容å¨å¨ç«äºæ¿çç并åç¯å¢ä¸è¡¨ç°åºæçä½ä¸çåå æ¯å 为ææ访é®Hashtableç线ç¨é½å¿ é¡»ç«äºåä¸æéï¼åå¦å®¹å¨éæå¤æéï¼æ¯ä¸æéç¨äºé容å¨å ¶ä¸ä¸é¨åæ°æ®ï¼é£ä¹å½å¤çº¿ç¨è®¿é®å®¹å¨éä¸åæ°æ®æ®µçæ°æ®æ¶ï¼çº¿ç¨é´å°±ä¸ä¼åå¨éç«äºï¼ä»èå¯ä»¥æææé«å¹¶å访é®æçï¼è¿å°±æ¯ConcurrentHashMapæ使ç¨çéå段ææ¯ãé¦å å°æ°æ®åæä¸æ®µä¸æ®µåå¨ï¼ç¶åç»æ¯ä¸æ®µæ°æ®é ä¸æéï¼å½ä¸ä¸ªçº¿ç¨å ç¨é访é®å ¶ä¸ä¸ä¸ªæ®µæ°æ®çæ¶åï¼å ¶å®æ®µçæ°æ®ä¹è½è¢«å ¶å®çº¿ç¨è®¿é®ã
ä¸å¾äºè§£ConcurrentHashMapåºå±åç
1ãConcurrentHashMapåºå±æ°æ®ç»ææ¯ä¸ä¸ªæ°ç»table
2ãtableæ°ç»ä¸æçååé¾è¡¨æ红é»æ
3ãnew ConcurrentHashMap();å¦æ没ææå®é¿åº¦çè¯ï¼é»è®¤æ¯ï¼å¹¶ä¸æ°ç»é¿åº¦å¿ é¡»æ¯2çn次å¹ï¼è¥èªå®ä¹åå§åçé¿åº¦ä¸æ¯2çn次å¹ï¼é£ä¹å¨åå§åæ°ç»æ¶ï¼ä¼å§æ°ç»é¿åº¦è®¾ç½®ä¸ºå¤§äºèªå®ä¹é¿åº¦çæè¿ç2çn次å¹ãï¼å¦ï¼èªå®ä¹é¿åº¦ä¸º7ï¼é£ä¹å®é åå§åæ°ç»åçé¿åº¦ä¸º8ï¼
4ãåºå±æ¯ä½¿ç¨synchronizedä½ä¸ºåæ¥éï¼å¹¶ä¸éçç²åº¦æ¯æ°ç»çå ·ä½ç´¢å¼ä½ï¼æäºäººç§°ä¹ä¸ºå段éï¼ã
5ãNodeèç¹ï¼hash>0ï¼å½hashå²çªæ¶ï¼ä¼å½¢æä¸ä¸ªååé¾è¡¨æå¨æ°ç»ä¸ã
6ãForwardindNodeï¼ç»§æ¿è³Nodeï¼å ¶hash=-1ï¼è¡¨ç¤ºå½åç´¢å¼ä½çæ°æ®å·²ç»è¢«è¿ç§»å°æ°æ°ç»ä¸äº
7ãReservationNodeï¼ç»§æ¿è³Nodeï¼å ¶hash=-3ï¼è¡¨ç¤ºå½åç´¢å¼ä½è¢«å ç¨äºï¼computeæ¹æ³ï¼
8ãTreenBinï¼ç»§æ¿è³Nodeï¼å ¶hash=-2ï¼ä»£è¡¨å½åç´¢å¼ä¸æçä¸é¢çº¢é»æ
9ãlockState为ConcurrentHashMapåºå±èªå·±å®ç°çåºäºcasç读åéï¼éç²åº¦æ¯å ·ä½çæé¢æ ãå½å红é»æ è¿è¡å¢ï¼å æä½æ¶ï¼é¦å ä¼å ä¸syncåæ¥éï¼ç¶åèªå¹³è¡¡çæ¶åä¼ä¸lockStateåéï¼å½è¯»çº¢é»æ çæ¶åï¼ä¼ä¸lockState读éï¼ç¶åå¤ææ¤æ¶æ¯å¦æ线ç¨æ£ææåéï¼ææ¯å¦æ线ç¨æ£å¨çå¾ è·ååéï¼è¥æï¼å读线ç¨ä¼ç´æ¥å»è¯»ååé¾è¡¨ï¼èä¸æ¯å»è¯»çº¢é»æ ã
ãTreeNodeï¼hash>0ï¼ä¸ºçº¢é»æ å ·ä½èç¹ãTreeBinçroot代表红é»æ çæ ¹èç¹ï¼first代表ååé¾è¡¨ç第ä¸ä¸ªèç¹
ãååé¾è¡¨ï¼æ建红é»æ æ¶è¿ç»´æ¤äºä¸ä¸ªååé¾è¡¨ï¼å ¶ç®ç为ï¼(1)å½å¹¶åå读åä¸é¢çº¢é»æ çæ¶åï¼è¯»çº¿ç¨å¤æå°æ线ç¨æ£ææåéï¼é£ä¹ä¼è·å»è¯»åååé¾è¡¨ï¼é¿å å 为并åå读导è´è¯»çº¿ç¨çå¾ æé»å¡ã(2)å½æ©å®¹çæ¶åï¼ä¼å»éåååé¾è¡¨ï¼éæ°è®¡ç®èç¹hashï¼æ ¹æ®æ°çhashå¤ææ¾å¨æ°æ°ç»çé«ä½è¿æ¯å°ä½
1ã触åæ©å®¹çè§åï¼
1ï¼æ·»å å®å ç´ åï¼è¥å¤æå°å½å容å¨å ç´ ä¸ªæ°è¾¾å°äºæ©å®¹çéå¼ï¼æ°ç»é¿åº¦*0.ï¼ï¼åä¼è§¦åæ©å®¹
2ï¼æ·»å å®å ç´ åï¼æå¨çååé¾è¡¨é¿åº¦>=8ï¼åä¼è½¬ä¸ºçº¢é»æ ï¼ä¸è¿å¨è½¬çº¢é»æ ä¹åä¼å¤ææ°ç»é¿åº¦æ¯å¦å°äºï¼è¥å°äºåä¼è§¦åæ©å®¹
2ãtable为æ©å®¹åçæ°ç»ï¼nextTable为æ©å®¹ä¸å建çæ°æ°ç»ï¼è¿ç§»æ°æ®å®æ¯åéè¦å°nextTableèµå¼ç»table
3ãæ©å®¹åçæ°ç»æ¯å ç´ ç»é¿åº¦ç2å
4ãlnï¼hnåå«è¡¨ç¤ºé«ä½åä½ä½çé¾è¡¨ï¼é«é¾ï¼ä½é¾ï¼ãå³ï¼æ©å®¹æ¶ï¼ä¼ç¨n&h==0æ¥å¤æå ç´ åºè¯¥æ¾å¨æ°æ°ç»çiä½ç½®è¿æ¯i+nä½ç½®ãnï¼å ç´ ç»é¿åº¦ï¼hï¼å ç´ hashå¼ï¼iï¼å ç´ æå¨çåæ°ç»ç´¢å¼ä½ï¼ãè¿æ ·å°±ä¼åºç°æäºå ç´ ä¼è¢«æå¨ä½ä½ï¼æäºå ç´ ä¼è¢«æå¨é«ä½ï¼ä»èè¾¾å°ææ£å ç´ çç®çã
5ã红é»æ æ©å®¹æ¶ï¼ä¼éåååé¾è¡¨ï¼å¹¶ä¸è®¡ç®n&h==0æ¥å¤æå ç´ æ¾å¨ä½ä½ï¼loï¼è¿æ¯é«ä½ï¼hoï¼ï¼ç¡®å®å®å ç´ çå»å¤ä¹åï¼ä¼å¤æåå«å¤æ两个ååé¾è¡¨ï¼lo,hoï¼çé¿åº¦æ¯å¦å¤§äº6ï¼è¥å¤§äº6åå°è¿æ¯ä»¥ä¸é¢çº¢é»æ çç»ææå¨æ°ç»ä¸ï¼è¥<=6çè¯ï¼å转为ååé¾è¡¨ï¼æå¨æ°ç»ä¸
ConcurrentHashMap确实很复杂,这样学源码才简单
ConcurrentHashMap相较于HashMap在实现上更为复杂,主要涉及多线程环境下的并发安全、同步和锁的概念。虽然HashMap的原理主要围绕数组、链表、哈希碰撞和扩容,但在多线程场景下,这些知识还不够,需要对并发和同步有深入理解。
在实际编程中,HashMap经常被使用,而ConcurrentHashMap的使用频率却相对较低,这使得学习它的门槛变高。学习ConcurrentHashMap之前,关键在于理解HashMap的基本实现,特别是它在非线程安全情况下的操作,如数组初始化和putVal()方法。
HashMap的线程不安全问题主要表现在数组的懒加载和带if判断的put操作上,这可能导致数据一致性问题。为了解决这些问题,像HashTable和Collections.synchronizedMap()通过synchronized关键字加锁,但会导致性能下降。ConcurrentHashMap引入了CAS(Compare And Swap)技术,比如在initTable()方法中,通过volatile修饰的成员变量保证了数组初始化的线程安全。
ConcurrentHashMap在数组初始化、下标为空时使用CAS,而在有冲突时切换到synchronized,降低了锁的粒度,以提高效率。扩容是ConcurrentHashMap的难点,需要处理新旧数组的同步迁移问题,通过helpTransfer()方法和transfer()方法来确保线程安全。
总结来说,学习ConcurrentHashMap不仅是对HashMap知识的扩展,更是进入并发编程世界的重要一步。面试时,如果只问基本数据结构,那可能只需要了解HashMap;但若深入到ConcurrentHashMap,就涉及到了并发编程的核心技术,如CAS、同步和锁的管理。