线程安全的HashMap · 系统设计(System Design)



线程安全的HashMap · 系统设计(System Design)

  • 开地址法(Open addressing). 即所有元素在一个一维数组里,遇到冲突则按照一定规则向后跳,假设元素x原本在位置hash(x)%m(m表示数组长度),那么第i次冲突时位置变为Hi = [hash(x) + di] % m,其中di表示第i次冲突时人为加上去的偏移量。偏移量di一般有如下3种计算方法,

    +

    1. 线性探测法(Linear Probing)。非常简单,发现位子已经被占了,则向后移动1位,即di=id_i = i, i=1,2,3,...

      +

      该算法最大的优点在于计算速度快,对CPU高速缓存友好;其缺点也非常明显,假设3个元素x1,x2,x3的哈希值都相同,记为p, x1先来,查看位置p,是空,则x1被映射到位置p,x2后到达,查看位置p,发生第一次冲突,向后探测一下,即p+1,该位置为空,于是x2映射到p+1, 同理,计算x3的位置需要探测位置p, p+1, p+2,也就是说对于发生冲突的所有元素,在探测过程中会扎堆,导致效率低下,这种现象被称为clustering。

      +

    2. 二次探测法(Quadratic Probing)。di=ai2+bi+cd_i=ai^2+bi+c, 其中a,b,c为系数,did_i是i的二次函数,所以称为二次探测法。该算法的性能介于线性探测和双哈希之间。

      +

    3. 双哈希法(Double Hashing)。偏移量di由另一个哈希函数计算而来,设为hash2(x),则di=(hash2(x) % (m-1) + 1) * i
  • 拉链法(Chaining)。开一个定长数组,每个格子指向一个桶(Bucket, 可以用单链表或双向链表表示),对每个元素计算出哈希并取模,找到对应的桶,并插入该桶。发生冲突的元素会处于同一个桶中。

    +

JDK7和JDK8里java.util.HashMap采取了拉链法。

+

如何将基于拉链法的HashMap改造为线程安全的呢?有以下几个思路,

+

  • 将所有public方法都加上synchronized. 相当于设置了一把全局锁,所有操作都需要先获取锁,java.util.HashTable就是这么做的,性能很低
  • 由于每个桶在逻辑上是相互独立的,将每个桶都加一把锁,如果两个线程各自访问不同的桶,就不需要争抢同一把锁了。这个方案的并发性比单个全局锁的性能要好,不过锁的个数太多,也有很大的开销。
  • 锁分离(Lock Stripping)技术。第2个方法把锁的压力分散到了多个桶,理论上是可行的的,但是假设有1万个桶,就要新建1万个ReentrantLock实例,开销很大。可以将所有的桶均匀的划分为16个部分,每一部分成为一个段(Segment),每个段上有一把锁,这样锁的数量就降低到了16个。JDK 7里的java.util.concurrent.ConcurrentHashMap就是这个思路。
  • 在JDK8里,ConcurrentHashMap的实现又有了很大变化,它在锁分离的基础上,大量利用了了CAS指令。并且底层存储有一个小优化,当链表长度太长(默认超过8)时,链表就转换为红黑树。链表太长时,增删查改的效率比较低,改为红黑树可以提高性能。JDK8里的ConcurrentHashMap有6000多行代码,JDK7才1500多行。


Read full article from 线程安全的HashMap · 系统设计(System Design)


No comments:

Post a Comment

Labels

Algorithm (219) Lucene (130) LeetCode (97) Database (36) Data Structure (33) text mining (28) Solr (27) java (27) Mathematical Algorithm (26) Difficult Algorithm (25) Logic Thinking (23) Puzzles (23) Bit Algorithms (22) Math (21) List (20) Dynamic Programming (19) Linux (19) Tree (18) Machine Learning (15) EPI (11) Queue (11) Smart Algorithm (11) Operating System (9) Java Basic (8) Recursive Algorithm (8) Stack (8) Eclipse (7) Scala (7) Tika (7) J2EE (6) Monitoring (6) Trie (6) Concurrency (5) Geometry Algorithm (5) Greedy Algorithm (5) Mahout (5) MySQL (5) xpost (5) C (4) Interview (4) Vi (4) regular expression (4) to-do (4) C++ (3) Chrome (3) Divide and Conquer (3) Graph Algorithm (3) Permutation (3) Powershell (3) Random (3) Segment Tree (3) UIMA (3) Union-Find (3) Video (3) Virtualization (3) Windows (3) XML (3) Advanced Data Structure (2) Android (2) Bash (2) Classic Algorithm (2) Debugging (2) Design Pattern (2) Google (2) Hadoop (2) Java Collections (2) Markov Chains (2) Probabilities (2) Shell (2) Site (2) Web Development (2) Workplace (2) angularjs (2) .Net (1) Amazon Interview (1) Android Studio (1) Array (1) Boilerpipe (1) Book Notes (1) ChromeOS (1) Chromebook (1) Codility (1) Desgin (1) Design (1) Divide and Conqure (1) GAE (1) Google Interview (1) Great Stuff (1) Hash (1) High Tech Companies (1) Improving (1) LifeTips (1) Maven (1) Network (1) Performance (1) Programming (1) Resources (1) Sampling (1) Sed (1) Smart Thinking (1) Sort (1) Spark (1) Stanford NLP (1) System Design (1) Trove (1) VIP (1) tools (1)

Popular Posts