[toc]
JDK1.7分段代码锁的实现
和HashMap一样在1.7中ConcurrentHashMap的底层数据结构是数组加链表,和HashMap不同的是ConcurrentHashMap存放的数据是一段一段的,即由多个Segment组成,每个Segment都有类似的数组加链表的结构。
关于Segment:
ConturrentHashMap有三个参数:
- initialCapacity:容量,初始容量默认16
- loadFactor:负载因子,默认是0.75
- concurrentLevel:并发级别默认是16
其中concurrentLevel控制了Segment的个数,在一个ConcurrentHashMap创建后Sement的个数是不能变的,扩容过程改变的是每个Segement的大小。
Segement继承了重入锁ReentrantLock,有锁的功能,每个锁控制的是一段,当每个Segement越来越大时,锁的力度就变的很大。
- 优点:分段代码锁的优势保证了操作不同的map的时候可以并发执行,操作同段map的时候,进行锁竞争和等待,这相对于直接对这个map同步synchronized是有优势的。
- 缺点:在于分成多段会比较浪费内存空间(不连续,碎片化),操作map时竞争同一个分段锁的概率非常小,分段锁反而造成更新等操作长时间等待,当某个段很大时,分段性能会下降。
JDK1.8的ConcurrentHashMap实现
和hashMap一样采用了数组链表红黑树的形式数组进行扩容,链表可以转化为红黑树。
什么时候扩容?
- 当前容量超过阈值
- 当链表元素个数超过默认设定(8个),当数组大小还未超过64的时候,此时进行数组扩容,如果超过64则将链表转化为红黑树。
什么时候链表转为红黑树?
当数组大小超过64并且链表中元素个数超过默认8的时候链表转为红黑树,但链表长度小于等于6时候会将红黑树转为链表(红黑树保留链表特性)。
1.8的线程安全的实现
1.8的代码把数组中每个元素看成一个桶,可以看到大部分的CAS操作,加锁部分是对桶的头结点进行加锁粒度很小。
为什么弃用Segement而用Synchroniized
- 减少内存开销,如果使用ReentrantLock则需要节点继承AQS来获取同步支持,增加内存开销,而1.8只有头部节点需要进行同步
- 内部优化:synchronized是jvm直接支持的,jvm能够运行时做出相应的优化措施,锁粗化,锁消除,锁自旋,这使得synchronized能够随着JDK版本升级而不用改动代码前提下获得性能提升。
网友评论