线程安全Map——ConcurrentHashMap(JDK1.7)解析及与HashMap、HashTable的区别

1、底层数据结构是什么?

在研究ConcurrentHashMap的底层数据结构之前,我们先要明确ConcurrentHashMap类中的属性,其中,有两个最重要的属性,分别是HashEntry和Segment。

HashEntry是ConcurrentHashMap最基本的储存数据的单位,主要存储键值对,其源码是:

static final class HashEntry<K,V> {
        final int hash;
        final K key;
        volatile V value;
        volatile HashEntry<K,V> next;

        HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

可以看出是一个链表型数据结构。

而Segment是ConcurrentHashMap的一个内部类,继承自ReentrantLock类,实现了Serializable接口。

Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
            this.loadFactor = lf;
            this.threshold = threshold;
            this.table = tab;
        }

ReentrantLock是一种重入锁,说明Segment自身是带有锁机制的,可以保证线程安全,并且是可序列化的(Serializable)。
并且从其构造函数可以看出,它参数传的是HashEntry类型的一个数组,而HashEntry本身是个链表,所以每个Segment对象其实都是一个Map类似的数组加链表的结构。

在ConcurrentHashMap的构造函数中,会初始化一个Segment数组出来,所以ConcurrentHashMap类的底层数据结构模式应该如下图所示:
在这里插入图片描述

2、通过什么保证线程安全?

刚刚已经提到过,ConcurrentHashMap中有着Segment这个属性。而Segment继承自ReentrantLock,所以每一个Segment都是通过重入锁来保证Segment内部以HashEntry为基本单位的Map的线程安全。

ConcurrentHashMap是通过Segment下的put()方法存入数据的,在每一次进行put()操作时,都会加一次锁:

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> first = entryAt(tab, index);
                for (HashEntry<K,V> e = first;;) {
                    if (e != null) {
                        K k;
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return oldValue;
        }

这样就能保证在多线程并发执行存入数据时的线程安全性。

3、HashTable和ConcurrentHashMap线程安全保证机制是否一样?

HashTable的线程安全保证机制是通过在方法前用synchronized关键字修饰,通过对当前对象加锁保证线程安全。但是当一个线程访问同步方法时,其他线程也访问同步方法时,可能会进入阻塞或轮询状态,这样就会将效率大大降低。

而ConcurrentHashMap是通过创建Segment数组(默认大小为16),以分段式的加锁方式来保证线程安全,每一把锁只锁其中的一部分数据,多线程访问不同数据段的数据,就不会存在锁竞争,这样在一个线程进行操作时,其他线程进行访问方法的时候也可以进行操作。

而且ConcurrentHashMap中的get()方法并没有加锁,这样就可以实现多线程同时读取,由此可见,ConcurrentHashMap的效率比HashTable的效率更高。

4、HashMap、HashTable和ConcurrentHashMap区别是什么?

HashMap与ConcurrentHashMap:

首先,HashMap是没有锁机制的,所以HashMap不是线程安全的。其次HashMap的键值对是可以存储null值的,而我们通过ConcurrentHashMap源码的分析可以看出:
ConcurrentHashMap对整个Segment数组进行分割,分割后的每一段都通过lock进行加锁,保证了线程安全。

HashEntry<K,V> node = tryLock() ? null :

而且ConcurrentHashMap中键值对都是不允许含有null值的。

HashTable与ConcurrentHashMap:

之前提到过,HashTable里使用的是synchronized关键字,这其实是对对象加锁,锁住的都是对象整体,一次锁住整个hash表,从而同一时刻只能有一个线程对其进行操作。当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。

个人觉得ConcurrentHashMap就相当于一个HashTable的集合,先是Segment [ ] (默认大小为16)中每一个Segment下面都是独立的lock,一次只锁住一个map,这样就能保证在多个线程在访问时的安全性。能够同时最少16个线程进行访问(读取时没有加锁,可保证多个线程对同一目标进行读取)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值