线程安全的使用HashMap

为什么hashMap不是线程安全的?
主要有两点原因:
一是HashMap使用的数据结构。
基于哈希表(数组+链表+红黑树)。注意一下在JDK1.8之后,当链表的长度大于8时,链表将转换为红黑树的结构存储。所有根据 hash 值计算的 bucket 一样的 key 会存储到同一个链表里(即产生了冲突)

如果多个线程同时使用put方法添加元素,而且假设正好存在两个 put 的 key 发生了碰撞(根据 hash 值计算的 bucket 一样),那么根据 HashMap 的实现,这两个 key 会添加到数组的同一个位置,这样最终就会发生其中一个线程的 put 的数据被覆盖。

二是HashMap的扩容机制
默认加载因子是0.75,默认数组大小是16,扩充算法是:当前数组容量<<1(相当于乘以2),扩大一倍,扩充次数过多,会影响性能,每次扩容表示哈希表重新散列(重新计算每个对象的存储位置),我们在开发中尽量要减少扩充次数带来的性能问题

如果多个线程同时检测到元素个数超过数组大小loadFactor ,这样就会发生多个线程同时对 Node 数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋给 table,也就是说其他线程的都会丢失,并且各自线程 put 的数据也丢失。

那现在看看如何线程安全的使用 HashMap。这个无非就是以下三种方式:
Hashtable
ConcurrentHashMap
Synchronized Map

//Hashtable
Map<String, String> hashtable = new Hashtable<>();
//synchronizedMap
Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());
//ConcurrentHashMap
Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();

Hashtable
HashTable 源码中是使用 synchronized 来保证线程安全的,比如实现get 方法和 put 方法时前面加了synchronized
所以当一个线程访问 HashTable 的同步方法时,其他线程如果也要访问同步方法,会被阻塞住。举个例子,当一个线程使用 put 方法时,另一个线程不但不可以使用 put 方法,连 get 方法都不可以。效率很低!
synchronizedMap
其封装的本质和 Hashtable 的实现是完全一致的,即对原Map本身的方法进行加锁,加锁的对象或者为外部指定共享对象mutex,或者为包装后的线程安全的Map本身。Hashtable 可以理解为 SynchronizedMap mutex=null 时候的特殊情况。因此这种同步方式的执行效率也是很低的
ConcurrentHashMap
HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,不仅保证了访问的线程安全性,而且在效率上有较大的提高。

猜你喜欢

转载自blog.csdn.net/weixin_38653290/article/details/86662207