Open kukyxs opened 5 years ago
在java中有普通集合、同步(线程安全)的集合、并发集合。普通集合通常性能最高,但是不保证多线程的安全性和并发的可靠性。线程安全集合仅仅是给集合添加了 synchronized 同步锁,严重牺牲了性能,而且对并发的效率就更低了,并发集合则通过复杂的策略不仅保证了多线程的安全又提高的并发时的效率。 参考阅读: ConcurrentHashMap 是线程安全的HashMap的实现,默认构造同样有initialCapacity 和loadFactor属性,不过还多了一个 concurrencyLevel属性,三属性默认值分别为16、0.75 及 16。其内部使用锁分段技术,维持这锁Segment的数组,在Segment数组中又存放着Entity[]数组,内部hash算法将数据较均匀分布在不同锁中。
put操作: 并没有在此方法上加上synchronized,首先对key.hashcode进行hash 操作,得到key的hash 值。hash 操作的算法和 map 也不同,根据此 hash 值计算并获取其对应的数组中的 Segment 对象(继承自ReentrantLock),接着调用此Segment对象的put方法来完成当前操作。 ConcurrentHashMap 基于concurrencyLevel划分出了多个Segment 来对key-value进行存储,从而避免每次put操作都得锁住整个数组。在默认的情况下,最佳情况下可允许16 个线程并发无阻塞的操作集合对象,尽可能地减少并发时的阻塞现象。
get(key): 首先对key.hashCode进行hash 操作,基于其值找到对应的Segment 对象,调用其get方法完成当前操作。而 Segment 的 get 操作首先通过 hash 值和对象数组大小减 1 的值进行按位与操作来获取数组上对应位置的HashEntry。在这个步骤中,可能会因为对象数组大小的改变,以及数组上对应位置的HashEntry 产生不一致性,那么ConcurrentHashMap 是如何保证的? 对象数组大小的改变只有在put操作时有可能发生,由于HashEntry对象数组对应的变量是volatile类型的,因此可以保证如HashEntry 对象数组大小发生改变,读操作可看到最新的对象数组大小。 在获取到了 HashEntry 对象后,怎么能保证它及其 next 属性构成的链表上的对象不会改变呢?这点ConcurrentHashMap 采用了一个简单的方式,即HashEntry 对象中的hash、key、next 属性都是final 的,这也就意味着没办法插入一个HashEntry对象到基于next属性构成的链表中间或末尾。这样就可以保证当获取到HashEntry对象后,其基于next 属性构建的链表是不会发生变化的。 ConcurrentHashMap 默认情况下采用将数据分为 16 个段进行存储,并且 16 个段分别持有各自不同的锁Segment,锁仅用于put 和 remove 等改变集合对象的操作,基于volatile 及 HashEntry 链表的不变性实现了读取的不加锁。这些方式使得ConcurrentHashMap 能够保持极好的并发支持,尤其是对于读远比插入和删除频繁的Map而言,而它采用的这些方法也可谓是对于Java内存模型、并发机制深刻掌握的体现。
推荐博客地址:http://m.oschina.net/blog/269037
在java中有普通集合、同步(线程安全)的集合、并发集合。普通集合通常性能最高,但是不保证多线程的安全性和并发的可靠性。线程安全集合仅仅是给集合添加了 synchronized 同步锁,严重牺牲了性能,而且对并发的效率就更低了,并发集合则通过复杂的策略不仅保证了多线程的安全又提高的并发时的效率。 参考阅读: ConcurrentHashMap 是线程安全的HashMap的实现,默认构造同样有initialCapacity 和loadFactor属性,不过还多了一个 concurrencyLevel属性,三属性默认值分别为16、0.75 及 16。其内部使用锁分段技术,维持这锁Segment的数组,在Segment数组中又存放着Entity[]数组,内部hash算法将数据较均匀分布在不同锁中。
put操作: 并没有在此方法上加上synchronized,首先对key.hashcode进行hash 操作,得到key的hash 值。hash 操作的算法和 map 也不同,根据此 hash 值计算并获取其对应的数组中的 Segment 对象(继承自ReentrantLock),接着调用此Segment对象的put方法来完成当前操作。 ConcurrentHashMap 基于concurrencyLevel划分出了多个Segment 来对key-value进行存储,从而避免每次put操作都得锁住整个数组。在默认的情况下,最佳情况下可允许16 个线程并发无阻塞的操作集合对象,尽可能地减少并发时的阻塞现象。
get(key): 首先对key.hashCode进行hash 操作,基于其值找到对应的Segment 对象,调用其get方法完成当前操作。而 Segment 的 get 操作首先通过 hash 值和对象数组大小减 1 的值进行按位与操作来获取数组上对应位置的HashEntry。在这个步骤中,可能会因为对象数组大小的改变,以及数组上对应位置的HashEntry 产生不一致性,那么ConcurrentHashMap 是如何保证的? 对象数组大小的改变只有在put操作时有可能发生,由于HashEntry对象数组对应的变量是volatile类型的,因此可以保证如HashEntry 对象数组大小发生改变,读操作可看到最新的对象数组大小。 在获取到了 HashEntry 对象后,怎么能保证它及其 next 属性构成的链表上的对象不会改变呢?这点ConcurrentHashMap 采用了一个简单的方式,即HashEntry 对象中的hash、key、next 属性都是final 的,这也就意味着没办法插入一个HashEntry对象到基于next属性构成的链表中间或末尾。这样就可以保证当获取到HashEntry对象后,其基于next 属性构建的链表是不会发生变化的。 ConcurrentHashMap 默认情况下采用将数据分为 16 个段进行存储,并且 16 个段分别持有各自不同的锁Segment,锁仅用于put 和 remove 等改变集合对象的操作,基于volatile 及 HashEntry 链表的不变性实现了读取的不加锁。这些方式使得ConcurrentHashMap 能够保持极好的并发支持,尤其是对于读远比插入和删除频繁的Map而言,而它采用的这些方法也可谓是对于Java内存模型、并发机制深刻掌握的体现。
推荐博客地址:http://m.oschina.net/blog/269037