ConcurrentHashMap介绍

1.为啥会出现ConcurrentHashMap?

原因:

原因1:线程不安全的HashMap
因为多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。

源代码:

final HashMap<String, String> map = new HashMap<String, String>(2);
 
        Thread t = new Thread(new Runnable() {
 
            @Override
 
            public void run() {
 
                for (int i = 0; i < 10000; i++) {
 
                    new Thread(new Runnable() {
 
                        @Override
 
                        public void run() {
 
                            map.put(UUID.randomUUID().toString(), "");
 
                        }
 
                    }, "ftf" + i).start();
 
                }
 
            }
 
        }, "ftf");
 
        t.start();
 
        t.join();



原因二:效率低下的HashTable容器

HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。


2.ConcurrentHashMap概念

HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成。 Segment 是一种可重入锁 ReentrantLock, ConcurrentHashMap 里扮演锁的角色, HashEntry 则用于存储键值对数据。一个 ConcurrentHashMap 里包含一个 Segment 数组, Segment 的结构和 HashMap 类似,是一种数组和链表结构, 一个 Segment 里包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素, 每个 Segment 守护者一个 HashEntry 数组里的元素 , 当对 HashEntry 数组的数据进行修改时,必须首先获得它对应的 Segment 锁。
3. ConcurrentHashMap的初始化

ConcurrentHashMap初始化方法是通过initialCapacity,loadFactor,concurrencyLevel几个参数来初始化segments数组,段偏移量segmentShift,段掩码segmentMask和每个segment里的HashEntry数组。

if (concurrencyLevel > MAX_SEGMENTS)
 
concurrencyLevel = MAX_SEGMENTS;
 
// Find power-of-two sizes best matching arguments
 
int sshift = 0;
 
int ssize = 1;
 
while (ssize < concurrencyLevel) {
 
++sshift;
 
ssize <<= 1;
 
}
 
segmentShift = 32 - sshift;
 
segmentMask = ssize - 1;
 
this.segments = Segment.newArray(ssize);

由上面的代码可知segments数组的长度ssize通过concurrencyLevel计算得出。为了能通过按位与的哈希算法来定位segments数组的索引,必须保证segments数组的长度是2N次方(power-of-twosize),所以必须计算出一个是大于或等于concurrencyLevel的最小的2N次方值来作为segments数组的长度。假如concurrencyLevel等于141516ssize都会等于16,即容器里锁的个数也是16。注意concurrencyLevel的最大大小是65535,意味着segments数组的长度最大为65536,对应的二进制是16位。


初始化segmentShiftsegmentMask。这两个全局变量在定位segment时的哈希算法里需要使用,sshift等于ssize1向左移位的次数,在默认情况下concurrencyLevel等于161需要向左移位移动4次,所以sshift等于4segmentShift用于定位参与hash运算的位数,segmentShift等于32sshift,所以等于28,这里之所以用32是因为ConcurrentHashMap里的hash()方法输出的最大数是32位的,后面的测试中我们可以看到这点。segmentMask是哈希运算的掩码,等于ssize1,即15,掩码的二进制各个位的值都是1。因为ssize的最大长度是65536,所以segmentShift最大值是16segmentMask最大值是65535,对应的二进制是16位,每个位都是1



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值