当前位置: 首页 > news >正文

网站建设管理方案重庆响应式网站多少钱

网站建设管理方案,重庆响应式网站多少钱,手机移动开发技术,做网站准备什么问题简介#xff1a; 他山之石可以攻玉#xff0c;这些巧妙的设计思想非常有借鉴价值#xff0c;可谓是最佳实践。然而#xff0c;大多数有关Java Map原理的科普类文章都是专注于“点”#xff0c;并没有连成“线”#xff0c;甚至形成“网状结构”。因此#xff0c;本文基于…简介 他山之石可以攻玉这些巧妙的设计思想非常有借鉴价值可谓是最佳实践。然而大多数有关Java Map原理的科普类文章都是专注于“点”并没有连成“线”甚至形成“网状结构”。因此本文基于个人理解对所阅读的部分源码进行了分类与总结归纳出Map中的几个核心特性。 作者 | 子澐 来源 | 阿里技术公众号 最近拜读了一些Java Map的相关源码不得不惊叹于JDK开发者们的鬼斧神工。他山之石可以攻玉这些巧妙的设计思想非常有借鉴价值可谓是最佳实践。然而大多数有关Java Map原理的科普类文章都是专注于“点”并没有连成“线”甚至形成“网状结构”。因此本文基于个人理解对所阅读的部分源码进行了分类与总结归纳出Map中的几个核心特性包括自动扩容、初始化与懒加载、哈希计算、位运算与并发并结合源码进行深入讲解希望看完本文的你也能从中获取到些许收获本文默认采用JDK1.8中的HashMap。 一 自动扩容 最小可用原则容量超过一定阈值便自动进行扩容。扩容是通过resize方法来实现的。扩容发生在putVal方法的最后即写入元素之后才会判断是否需要扩容操作当自增后的size大于之前所计算好的阈值threshold即执行resize操作。 通过位运算1进行容量扩充即扩容1倍同时新的阈值newThr也扩容为老阈值的1倍。 扩容时总共存在三种情况 哈希桶数组中某个位置只有1个元素即不存在哈希冲突时则直接将该元素copy至新哈希桶数组的对应位置即可。哈希桶数组中某个位置的节点为树节点时则执行红黑树的扩容操作。哈希桶数组中某个位置的节点为普通节点时则执行链表扩容操作在JDK1.8中为了避免之前版本中并发扩容所导致的死链问题引入了高低位链表辅助进行扩容操作。在日常的开发过程中会遇到一些bad case比如 HashMap hashMap new HashMap(2); hashMap.put(1, 1); hashMap.put(2, 2); hashMap.put(3, 3); 当hashMap设置最后一个元素3的时候会发现当前的哈希桶数组大小已经达到扩容阈值2*0.751.5紧接着会执行一次扩容操作因此此类的代码每次运行的时候都会进行一次扩容操作效率低下。在日常开发过程中一定要充分评估好HashMap的大小尽可能保证扩容的阈值大于存储元素的数量减少其扩容次数。 二 初始化与懒加载 初始化的时候只会设置默认的负载因子并不会进行其他初始化的操作在首次使用的时候才会进行初始化。当new一个新的HashMap的时候不会立即对哈希数组进行初始化而是在首次put元素的时候通过resize()方法进行初始化。 resize()中会设置默认的初始化容量DEFAULT_INITIAL_CAPACITY为16扩容的阈值为0.75*16 12即哈希桶数组中元素达到12个便进行扩容操作。 最后创建容量为16的Node数组并赋值给成员变量哈希桶table即完成了HashMap的初始化操作。 三 哈希计算 哈希表以哈希命名足以说明哈希计算在该数据结构中的重要程度。而在实现中JDK并没有直接使用Object的native方法返回的hashCode作为最终的哈希值而是进行了二次加工。 以下分别为HashMap与ConcurrentHashMap计算hash值的方法核心的计算逻辑相同都是使用key对应的hashCode与其hashCode右移16位的结果进行异或操作。此处将高16位与低16位进行异或的操作称之为扰动函数目的是将高位的特征融入到低位之中降低哈希冲突的概率。 举个例子来理解下扰动函数的作用 hashCode(key1) 0000 0000 0000 1111 0000 0000 0000 0010 hashCode(key2) 0000 0000 0000 0000 0000 0000 0000 0010 若HashMap容量为4在不使用扰动函数的情况下key1与key2的hashCode注定会冲突后两位相同均为01。 经过扰动函数处理后可见key1与key2 hashcode的后两位不同上述的哈希冲突也就避免了。 hashCode(key1) ^ (hashCode(key1) 16) 0000 0000 0000 1111 0000 0000 0000 1101hashCode(key2) ^ (hashCode(key2) 16) 0000 0000 0000 0000 0000 0000 0000 0010这种增益会随着HashMap容量的减少而增加。《An introduction to optimising a hashing strategy》文章中随机选取了哈希值不同的352个字符串当HashMap的容量为2^9时使用扰动函数可以减少10%的碰撞可见扰动函数的必要性。 此外ConcurrentHashMap中经过扰乱函数处理之后需要与HASH_BITS做与运算HASH_BITS为0x7ffffff即只有最高位为0这样运算的结果使hashCode永远为正数。在ConcurrentHashMap中预定义了几个特殊节点的hashCode如MOVED、TREEBIN、RESERVED它们的hashCode均定义为负值。因此将普通节点的hashCode限定为正数也就是为了防止与这些特殊节点的hashCode产生冲突。 1 哈希冲突 通过哈希运算可以将不同的输入值映射到指定的区间范围内随之而来的是哈希冲突问题。考虑一个极端的case假设所有的输入元素经过哈希运算之后都映射到同一个哈希桶中那么查询的复杂度将不再是O(1)而是O(n)相当于线性表的顺序遍历。因此哈希冲突是影响哈希计算性能的重要因素之一。哈希冲突如何解决呢主要从两个方面考虑一方面是避免冲突另一方面是在冲突时合理地解决冲突尽可能提高查询效率。前者在上面的章节中已经进行介绍即通过扰动函数来增加hashCode的随机性避免冲突。针对后者HashMap中给出了两种方案拉链表与红黑树。 拉链表 在JDK1.8之前HashMap中是采用拉链表的方法来解决冲突即当计算出的hashCode对应的桶上已经存在元素但两者key不同时会基于桶中已存在的元素拉出一条链表将新元素链到已存在元素的前面。当查询存在冲突的哈希桶时会顺序遍历冲突链上的元素。同一key的判断逻辑如下图所示先判断hash值是否相同再比较key的地址或值是否相同。 1死链 在JDK1.8之前HashMap在并发场景下扩容时存在一个bug形成死链导致get该位置元素的时候会死循环使CPU利用率高居不下。这也说明了HashMap不适于用在高并发的场景高并发应该优先考虑JUC中的ConcurrentHashMap。然而精益求精的JDK开发者们并没有选择绕过问题而是选择直面问题并解决它。在JDK1.8之中引入了高低位链表双端链表。 什么是高低位链表呢在扩容时哈希桶数组buckets会扩容一倍以容量为8的HashMap为例原有容量8扩容至16将[0, 7]称为低位[8, 15]称为高位低位对应loHead、loTail高位对应hiHead、hiTail。 扩容时会依次遍历旧buckets数组的每一个位置上面的元素 若不存在冲突则重新进行hash取模并copy到新buckets数组中的对应位置。若存在冲突元素则采用高低位链表进行处理。通过e.hash oldCap来判断取模后是落在高位还是低位。举个例子假设当前元素hashCode为0001忽略高位其运算结果等于0说明扩容后结果不变取模后还是落在低位[0, 7]即0001 1000 0000还是原位置再用低位链表将这类的元素链接起来。假设当前元素的hashCode为1001 其运算结果不为0即1001 1000 1000 扩容后会落在高位新的位置刚好是旧数组索引1 旧数据长度8 9再用高位链表将这些元素链接起来。最后将高低位链表的头节点分别放在扩容后数组newTab的指定位置上即完成了扩容操作。这种实现降低了对共享资源newTab的访问频次先组织冲突节点最后再放入newTab的指定位置。避免了JDK1.8之前每遍历一个元素就放入newTab中从而导致并发扩容下的死链问题。红黑树 在JDK1.8之中HashMap引入了红黑树来处理哈希冲突问题而不再是拉链表。那么为什么要引入红黑树来替代链表呢虽然链表的插入性能是O(1)但查询性能确是O(n)当哈希冲突元素非常多时这种查询性能是难以接受的。因此在JDK1.8中如果冲突链上的元素数量大于8并且哈希桶数组的长度大于64时会使用红黑树代替链表来解决哈希冲突此时的节点会被封装成TreeNode而不再是NodeTreeNode其实继承了Node以利用多态特性使查询具备O(logn)的性能。 这里简单地回顾一下红黑树它是一种平衡的二叉树搜索树类似地还有AVL树。两者核心的区别是AVL树追求“绝对平衡”在插入、删除节点时成本要高于红黑树但也因此拥有了更好的查询性能适用于读多写少的场景。然而对于HashMap而言读写操作其实难分伯仲因此选择红黑树也算是在读写性能上的一种折中。 四 位运算 1 确定哈希桶数组大小 找到大于等于给定值的最小2的整数次幂。tableSizeFor根据输入容量大小cap来计算最终哈希桶数组的容量大小找到大于等于给定值cap的最小2的整数次幂。乍眼一看这一行一行的位运算让人云里雾里莫不如采用类似找规律的方式来探索其中的奥秘。 当cap为3时计算过程如下 cap 3 n 2 n | n 1 010 | 001 011 n 3 n | n 2 011 | 000 011 n 3 n | n 4 011 | 000 011 n 3 …. n n 1 4 当cap为5时计算过程如下 cap 5 n 4 n | n 1 0100 | 0010 0110 n 6 n | n 2 0110 | 0001 0111 n 7 …. n n 1 8 因此计算的意义在于找到大于等于cap的最小2的整数次幂。整个过程是找到cap对应二进制中最高位的1然后每次以2倍的步长依次移位1、2、4、8、16复制最高位1到后面的所有低位把最高位1后面的所有位全部置为1最后进行1即完成了进位。 类似二进制位的变化过程如下 0100 1010 0111 1111 1000 0000 找到输入cap的最小2的整数次幂作为最终容量可以理解为最小可用原则尽可能地少占用空间但是为什么必须要2的整数次幂呢答案是为了提高计算与存储效率使每个元素对应hash值能够准确落入哈希桶数组给定的范围区间内。确定数组下标采用的算法是 hash (n - 1)n即为哈希桶数组的大小。由于其总是2的整数次幂这意味着n-1的二进制形式永远都是0000111111的形式即从最低位开始连续出现多个1该二进制与任何值进行运算都会使该值映射到指定区间[0, n-1]。比如当n8时n-1对应的二进制为0111任何与0111进行运算都会落入[0,7]的范围内即落入给定的8个哈希桶中存储空间利用率100%。举个反例当n7n-1对应的二进制为0110任何与0110进行运算会落入到第0、6、4、2个哈希桶而不是[0,6]的区间范围内少了1、3、5三个哈希桶这导致存储空间利用率只有不到60%同时也增加了哈希碰撞的几率。 2 ASHIFT偏移量计算 获取给定值的最高有效位数移位除了能够进行乘除运算还能用于保留高、低位操作右移保留高位左移保留低位。ConcurrentHashMap中的ABASEASHIFT是用来计算哈希数组中某个元素在实际内存中的初始位置ASHIFT采取的计算方式是31与scale前导0的数量做差也就是scale的实际位数-1。scale就是哈希桶数组Node[]中每个元素的大小通过((long)i ASHIFT) ABASE)进行计算便可得到数组中第i个元素的起始内存地址。 我们继续看下前导0的数量是怎么计算出来的numberOfLeadingZeros是Integer的静态方法还是沿用找规律的方式一探究竟。 假设 i 0000 0000 0000 0100 0000 0000 0000 0000n 1 i 16 0000 0000 0000 0000 0000 0000 0000 0100 不为0i 24 0000 0000 0000 0000 0000 0000 0000 0000 等于0 右移了24位等于0说明24位到31位之间肯定全为0即n 1 8 9由于高8位全为0并且已经将信息记录至n中因此可以舍弃高8位即 i 8。此时 i 0000 0100 0000 0000 0000 0000 0000 0000 类似地i 28 也等于0说明28位到31位全为0n 9 4 13舍弃高4位。此时 i 0100 0000 0000 0000 0000 0000 0000 0000 继续运算 i 30 0000 0000 0000 0000 0000 0000 0000 0001 不为0 i 31 0000 0000 0000 0000 0000 0000 0000 0000 等于0 最终可得出n 13即有13个前导0。n - i 31是检查最高位31位是否是1因为n初始化为1如果最高位是1则不存在前置0即n n - 1 0。 总结一下以上的操作其实是基于二分法的思想来定位二进制中1的最高位先看高16位若为0说明1存在于低16位反之存在高16位。由此将搜索范围由32位确切的说是31位减少至16位进而再一分为二校验高8位与低8位以此类推。 计算过程中校验的位数依次为16、8、4、2、1加起来刚好为31。为什么是31不是32呢因为前置0的数量为32的情况下i只能为0在前面的if条件中已经进行过滤。这样一来非0值的情况下前置0只能出现在高31位因此只需要校验高31位即可。最终用总位数减去计算出来的前导0的数量即可得出二进制的最高有效位数。代码中使用的是31 - Integer.numberOfLeadingZeros(scale)而不是总位数32这是为了能够得到哈希桶数组中第i个元素的起始内存地址方便进行CAS等操作。 五 并发 1 悲观锁 全表锁 HashTable中采用了全表锁即所有操作均上锁串行执行如下图中的put方法所示采用synchronized关键字修饰。这样虽然保证了线程安全但是在多核处理器时代也极大地影响了计算性能这也致使HashTable逐渐淡出开发者们的视野。 分段锁 针对HashTable中锁粒度过粗的问题在JDK1.8之前ConcurrentHashMap引入了分段锁机制。整体的存储结构如下图所示在原有结构的基础上拆分出多个segment每个segment下再挂载原来的entry上文中经常提到的哈希桶数组每次操作只需要锁定元素所在的segment不需要锁定整个表。因此锁定的范围更小并发度也会得到提升。 2 乐观锁 SynchronizedCAS 虽然引入了分段锁的机制即可以保证线程安全又可以解决锁粒度过粗导致的性能低下问题但是对于追求极致性能的工程师来说这还不是性能的天花板。因此在JDK1.8中ConcurrentHashMap摒弃了分段锁使用了乐观锁的实现方式。放弃分段锁的原因主要有以下几点 使用segment之后会增加ConcurrentHashMap的存储空间。当单个segment过大时并发性能会急剧下降。 ConcurrentHashMap在JDK1.8中的实现废弃了之前的segment结构沿用了与HashMap中的类似的Node数组结构。 ConcurrentHashMap中的乐观锁是采用synchronizedCAS进行实现的。这里主要看下put的相关代码。 当put的元素在哈希桶数组中不存在时则直接CAS进行写操作。 这里涉及到了两个重要的操作tabAt与casTabAt。可以看出这里面都使用了Unsafe类的方法。Unsafe这个类在日常的开发过程中比较罕见。我们通常对Java语言的认知是Java语言是安全的所有操作都基于JVM在安全可控的范围内进行。然而Unsafe这个类会打破这个边界使Java拥有C的能力可以操作任意内存地址是一把双刃剑。这里使用到了前文中所提到的ASHIFT来计算出指定元素的起始内存地址再通过getObjectVolatile与compareAndSwapObject分别进行取值与CAS操作。 在获取哈希桶数组中指定位置的元素时为什么不能直接get而是要使用getObjectVolatile呢因为在JVM的内存模型中每个线程有自己的工作内存也就是栈中的局部变量表它是主存的一份copy。因此线程1对某个共享资源进行了更新操作并写入到主存而线程2的工作内存之中可能还是旧值脏数据便产生了。Java中的volatile是用来解决上述问题保证可见性任意线程对volatile关键字修饰的变量进行更新时会使其它线程中该变量的副本失效需要从主存中获取最新值。虽然ConcurrentHashMap中的Node数组是由volatile修饰的可以保证可见性但是Node数组中元素是不具备可见性的。因此在获取数据时通过Unsafe的方法直接到主存中拿保证获取的数据是最新的。 继续往下看put方法的逻辑当put的元素在哈希桶数组中存在并且不处于扩容状态时则使用synchronized锁定哈希桶数组中第i个位置中的第一个元素f头节点2接着进行double check类似于DCL单例模式的思想。校验通过后会遍历当前冲突链上的元素并选择合适的位置进行put操作。此外ConcurrentHashMap也沿用了HashMap中解决哈希冲突的方案链表红黑树。这里只有在发生哈希冲突的情况下才使用synchronized锁定头节点其实是比分段锁更细粒度的锁实现只在特定场景下锁定其中一个哈希桶降低锁的影响范围。 Java Map针对并发场景解决方案的演进方向可以归结为从悲观锁到乐观锁从粗粒度锁到细粒度锁这也可以作为我们在日常并发编程中的指导方针。 3 并发求和 CounterCell是JDK1.8中引入用来并发求和的利器而在这之前采用的是【尝试无锁求和】【冲突时加锁重试】的策略。看下CounterCell的注释它是改编自LongAdder和Striped64。我们先看下求和操作其实就是取baseCount作为初始值然后遍历CounterCell数组中的每一个cell将各个cell的值进行累加。这里额外说明下sun.misc.Contender注解的作用它是Java8中引入用来解决缓存行伪共享问题的。什么是伪共享呢简单说下考虑到CPU与主存之间速度的巨大差异在CPU中引入了L1、L2、L3多级缓存缓存中的存储单位是缓存行缓存行大小为2的整数次幂字节32-256个字节不等最常见的是64字节。因此这将导致不足64字节的变量会共享同一个缓存行其中一个变量失效会影响到同一个缓存行中的其他变量致使性能下降这就是伪共享问题。考虑到不同CPU的缓存行单位的差异性Java8中便通过该注解将这种差异性屏蔽根据实际缓存行大小来进行填充使被修饰的变量能够独占一个缓存行。 再来看下CounterCell是如何实现计数的每当map中的容量有变化时会调用addCount进行计数核心逻辑如下 当counterCells不为空或counterCells为空且对baseCount进行CAS操作失败时进入到后续计数处理逻辑否则对baseCount进行CAS操作成功直接返回。后续计数处理逻辑中会调用核心计数方法fullAddCount但需要满足以下4个条件中的任意一个1、counterCells为空2、counterCells的size为03、counterCells对应位置上的counterCell为空4、CAS更新counterCells对应位置上的counterCell失败。这些条件背后的语义是当前情况下计数已经或曾经出现过并发冲突需要优先借助于CounterCell来解决。若counterCells与对应位置上的元素已经初始化条件4则先尝试CAS进行更新若失败则调用fullAddCount继续处理。若counterCells与对应位置上的元素未初始化完成条件1、2、3也要调用AddCount进行后续处理。这里确定cell下标时采用了ThreadLocalRandom.getProbe()作为哈希值这个方法返回的是当前Thread中threadLocalRandomProbe字段的值。而且当哈希值冲突时还可以通过advanceProbe方法来更换哈希值。这与HashMap中的哈希值计算逻辑不同因为HashMap中要保证同一个key进行多次哈希计算的哈希值相同并且能定位到对应的value即便两个key的哈希值冲突也不能随便更换哈希值只能采用链表或红黑树处理冲突。然而在计数场景我们并不需要维护key-value的关系只需要在counterCells中找到一个合适的位置放入计数cell位置的差异对最终的求和结果是没有影响的因此当冲突时可以基于随机策略更换一个哈希值来避免冲突。接着我们来看下核心计算逻辑fullAddCount代码还是比较多的核心流程是通过一个死循环来实现的循环体中包含了3个处理分支为了方便讲解我将它们依次定义A、B、C。 A表示counterCells已经初始化完成因此可以尝试更新或创建对应位置的CounterCell。B表示counterCells未初始化完成且无冲突拿到cellsBusy锁则加锁初始化counterCells初始容量为2。C表示counterCells未初始化完成且有冲突未能拿到cellsBusy锁则CAS更新baseCountbaseCount在求和时也会被算入到最终结果中这也相当于是一种兜底策略既然counterCells正在被其他线程锁定那当前线程也没必要再等待了直接尝试使用baseCount进行累加。 其中A分支中涉及到的操作又可以拆分为以下几点 a1对应位置的CounterCell未创建采用锁Double Check的策略尝试创建CounterCell失败的话则continue进行重试。这里面采用的锁是cellsBusy它保证创建CounterCell并放入counterCells时一定是串行执行避免重复创建其实就是使用了DCL单例模式的策略。在CounterCells的创建、扩容中都需要使用该锁。a2冲突检测变量wasUncontended是调用方addCount中传入的表示前置的CAS更新cell失败有冲突需要更换哈希值【a7】后继续重试。a3对应位置的CounterCell不为空直接CAS进行更新。 a4 冲突检测当counterCells的引用值不等于当前线程对应的引用值时说明有其他线程更改了counterCells的引用出现冲突则将collide设为false下次迭代时可进行扩容。容量限制counterCells容量的最大值为大于等于NCPU实际机器CPU核心的数量的最小2的整数次幂当达到容量限制时后面的扩容分支便永远不会执行。这里限制的意义在于真实并发度是由CPU核心来决定当counterCells容量与CPU核心数量相等时理想情况下就算所有CPU核心在同时运行不同的计数线程时都不应该出现冲突每个线程选择各自的cell进行处理即可。如果出现冲突一定是哈希值的问题因此采取的措施是重新计算哈希值a7而不是通过扩容来解决。时间换空间避免不必要的存储空间浪费非常赞的想法~a5更新扩容标志位下次迭代时将会进行扩容。a6进行加锁扩容每次扩容1倍。a7更换哈希值。 private final void fullAddCount(long x, boolean wasUncontended) {int h;// 初始化probeif ((h ThreadLocalRandom.getProbe()) 0) {ThreadLocalRandom.localInit(); // force initializationh ThreadLocalRandom.getProbe();wasUncontended true;}// 用来控制扩容操作boolean collide false; // True if last slot nonemptyfor (;;) {CounterCell[] as; CounterCell a; int n; long v;// 【A】counterCells已经初始化完毕if ((as counterCells) ! null (n as.length) 0) {// 【a1】对应位置的CounterCell未创建if ((a as[(n - 1) h]) null) {// cellsBusy其实是一个锁cellsBusy0时表示无冲突if (cellsBusy 0) { // Try to attach new Cell// 创建新的CounterCellCounterCell r new CounterCell(x); // Optimistic create// Double Check加锁通过CAS将cellsBusy设置1if (cellsBusy 0 U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {boolean created false;try { // Recheck under lockCounterCell[] rs; int m, j;// Double Checkif ((rs counterCells) ! null (m rs.length) 0 rs[j (m - 1) h] null) {// 将新创建的CounterCell放入counterCells中rs[j] r;created true;}} finally {// 解锁这里为什么不用CAS因为当前流程中需要在获取锁的前提下进行即串行执行因此不存在并发更新问题只需要正常更新即可cellsBusy 0;}if (created)break;// 创建失败则重试continue; // Slot is now non-empty}}// cellsBusy不为0说明被其他线程争抢到了锁还不能考虑扩容collide false;}//【a2】冲突检测else if (!wasUncontended) // CAS already known to fail// 调用方addCount中CAS更新cell失败有冲突则继续尝试CASwasUncontended true; // Continue after rehash//【a3】对应位置的CounterCell不为空直接CAS进行更新else if (U.compareAndSwapLong(a, CELLVALUE, v a.value, v x))break;//【a4】容量限制else if (counterCells ! as || n NCPU)// 说明counterCells容量的最大值为大于NCPU实际机器CPU核心的数量最小2的整数次幂。// 这里限制的意义在于并发度是由CPU核心来决定当counterCells容量与CPU核心数量相等时理论上讲就算所有CPU核心都在同时运行不同的计数线程时都不应该出现冲突每个线程选择各自的cell进行处理即可。如果出现冲突一定是哈希值的问题因此采取的措施是重新计算哈希值h ThreadLocalRandom.advanceProbe(h)而不是通过扩容来解决// 当n大于NCPU时后面的分支就不会走到了collide false; // At max size or stale// 【a5】更新扩容标志位else if (!collide)// 说明映射到cell位置不为空并且尝试进行CAS更新时失败了则说明有竞争将collide设置为true下次迭代时执行后面的扩容操作降低竞争度// 有竞争时执行rehash扩容当容量大于CPU核心时则停止扩容只进行rehashcollide true;// 【a6】加锁扩容else if (cellsBusy 0 U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {// 加锁扩容try {if (counterCells as) {// Expand table unless stale// 扩容1倍CounterCell[] rs new CounterCell[n 1];for (int i 0; i n; i)rs[i] as[i];counterCells rs;}} finally {cellsBusy 0;}collide false;continue; // Retry with expanded table}//【a7】更换哈希值h ThreadLocalRandom.advanceProbe(h);}// 【B】counterCells未初始化完成且无冲突则加锁初始化counterCellselse if (cellsBusy 0 counterCells as U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {boolean init false;try { // Initialize tableif (counterCells as) {CounterCell[] rs new CounterCell[2];rs[h 1] new CounterCell(x);counterCells rs;init true;}} finally {cellsBusy 0;}if (init)break;}// 【C】counterCells未初始化完成且有冲突则CAS更新baseCountelse if (U.compareAndSwapLong(this, BASECOUNT, v baseCount, v x))break; // Fall back on using base} CounterCell的设计很巧妙它的背后其实就是JDK1.8中的LongAdder。核心思想是在并发较低的场景下直接采用baseCount累加否则结合counterCells将不同的线程散列到不同的cell中进行计算尽可能地确保访问资源的隔离减少冲突。LongAdder相比较于AtomicLong中无脑CAS的策略在高并发的场景下能够减少CAS重试的次数提高计算效率。 六 结语 以上可能只是Java Map源码中的冰山一角但是基本包括了大部分的核心特性涵盖了我们日常开发中的大部分场景。读源码跟读书一样仿佛跨越了历史长河与作者进行近距离对话揣摩他的心思学习他的思想并加以传承。信息加工转化为知识并运用的过程是痛苦的但是痛并快乐着。 原文链接 本文为阿里云原创内容未经允许不得转载。
http://www.pierceye.com/news/287995/

相关文章:

  • 个人建站模板外国网站翻墙怎么做
  • 保定网站设计制作公司有经验的中山网站建设
  • 免费网站建设那个好wordpress本地怎么上传服务器
  • 自己做的网站加载慢WordPress模板首页文件是啥
  • 教学网站建设网站建设岗位有哪些
  • 网站建设合同的验收表网页网站的区别是什么
  • 开福区网站建设中wordpress调用所有栏目
  • 网站建设的流程是什么意思青岛做网站公司
  • 什么网站有项目做中铁建设企业门户网
  • 网站域名商代理商安卓商店
  • 深圳中英文网站建设广州知名设计公司排名
  • 一个公司设计网站怎么做的蜜桃传奇免费网页版
  • 网络推广网站培训班有人用wordpress默认主题
  • 网站建设 后台南宁做网站方案
  • 在线制作插画网站网站建设有哪些公司
  • 合肥的网站建设剂屏自己可以做开奖网站吗
  • 官网设计比较好看的网站学校网站建设对教学的意义
  • 济南建站推荐企汇优见效付款毕设代做网站招聘
  • 泰然建设网站免费软件app下载大全正能量网站
  • 张掖市网站建设北京代理记账财务公司
  • 中铁建设集团网站网络公司手机网站
  • 站长工具是什么意思建设银行网站 开户行怎么查
  • 做简历模板的网站都有哪些wordpress是啥东西
  • 网站流量渠道湖州做网站优化
  • 汽车网站哪个好预付的网站开发费用怎么入账
  • 网站代管理哪个网站有介绍拿到家做的手工活
  • 惊艳的网站网站建设实训过程报告
  • 秦皇岛做网站公司企业网站备案需要什么
  • 做网站必须开厂吗科协网站建设建议
  • 西宁 网站建设凡科做视频网站