HashMap、HashTable与ConcurrentHashMap

概述

为了解决HashMap 的线程不安全,有三种解决方法

  • 使用Collections.synchronizedMap(map)
  • 使用HashTable
  • 使用ConcurrentHashMap

考虑到并发度等问题用的最多的是最后一种,它的性能和效率明显高于前面两种。

1、Collections.synchronizedMap

再synchronizedMap内部维护了一个普通话对象map和排斥锁mutex

private final Map<K,V> m;     // Backing Map
final Object      mutex;        // Object on which to synchronize

SynchronizedMap(Map<K,V> m) {
    this.m = Objects.requireNonNull(m);
    mutex = this;
}

SynchronizedMap(Map<K,V> m, Object mutex) {
    this.m = m;
    this.mutex = mutex;
}

构造器有两个,如果为传入mutex,则将mutex置为this,如果传入了,mutex即为所设置的值。然后创建出synchronizedMap之后对map的操作全是加锁的了。

在这里插入图片描述

2、HashTable

HashTable是线程安全的,但是效率却不是那么乐观,其效率底下的原因是其底层对数据操作的时候加上了synchronized加锁
除了HashTable是线程安全的意外,HashTable与HashMap还有以下不同

  • 扩容直接不同,HashMap初始容量为16,每次扩容为原来的两倍,而HashTable初始容量为11,每次扩容为2*n+ 1;
  • HashMap允许键或值为null,而HashTable不允许键或值为null,因为HashTable底层实fail-safe的机制,会导致读到的数据不是最新的数据,也就是说当你通过get(k)获取对应的value时,如果获取到的是null时,你无法判断,它是put(k,v)的时候value为null,还是这个key从来没有做过映射。HashMap是非并发的,可以通过contains(key)来做这个判断。而支持并发的Map在调用m.contains(key)和m.get(key),m可能已经不同了。
  • HashMap是fail-fast的,而HashTable是fail-safe的
3、ConcurrentHashMap

ConcurrentHashMap再jdk1.7的实现:
在这里插入图片描述
是由Segment数组和HashEntry组成的,是基于数组加链表的,HashEntry使用了volatile修饰它的value和next指针,它的并发度高的原因是其采用分段锁技术,Segment继承自ReentrantLock,每个线程占用一个Segment,不会影响其他的Segment,也就是说它的并发度其实是容量大小。

其put()方法实现线程安全的方式如下:

  • ①、首先获取锁,如果获取失败则自旋尝试获取锁
  • ②、如果自旋次数达到了MAX_SCAN_RETRIES ,则阻塞获取锁,保证能获取到锁

get操作就比较简单了,先通过hash找到segment,然后再次hash找到指定位置的元素,因为HashEntry的value使用了volatile修饰保证了可见性,所以可以保证每次取数据的时候都是最新的值,get操作很高效,不需要加锁。

问题:ConcurrentHashMap再jdk1.7中存在和HashMap一样的问题,就是遍历链表效率低。

jdk1.8对ConcurrentHashMap的优化

ConcurrentHashMap再jdk1.8种摒弃了分段锁的使用,而是采用CAS + synchronized的方式来保证线程安全,同时把HashEntry改为了Node,但作用不变,Node的value和next指针仍然使用volatile修饰。

ConcurrentHashMap的put操作:

  • ①、获取key的hash值
  • ②、判断是否需要初始化table
  • ③、为当前的key定位出Node位置,如果为空表示当前位置可以写入数据,尝试利用CAS写入,失败则自旋保证成功
  • ④、如果当前的位置的hashcode == MOVED(MOVED 为-1),则进行扩容
  • ⑤、如果都不满足则利用synchronized写入数据
  • ⑥、树形化检测

get操作就比较简单了,直接根据hash值进行红黑树 或者链表的方式获取值即可。注意get操作全程不需要加锁是因为Node的成员val是用volatile修饰的和数组用volatile修饰没有关系。数组用volatile修饰主要是保证在数组扩容的时候保证可见性。

推荐阅读:
我把ConcurrentHashMap & HashTable的知识点都整理了一下

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值