【晃晃大联盟源码】【图文卡片源码】【抖音地铁源码】cocurrenthashmap源码

时间:2024-11-23 13:12:08 编辑:腾源源码网 来源:node 项目 源码

1.如何在java中使用ConcurrentHashMap
2.Java乐观锁的源码实现原理和典型案例
3.Java高并发编程实战7,ConcurrentHashMap详解
4.concurrenthashmap1.8源码如何详细解析?源码
5.震惊!ConcurrentHashMap里面也有死循环,源码作者留下的源码“彩蛋”了解一下?
6.HashMap、HashTable、ConcurrentHashMap的原理与区别

cocurrenthashmap源码

如何在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、同步和锁的管理。

搜索关键词:如何反编译java源码