非线程安全。(非同步)
怎么才能让 HashMap 变成线程安全的呢?
实现方法
我认为主要可以通过以下三种方法来实现:
1.替换成Hashtable
Hashtable通过对整个表上锁实现线程安全,因此效率比较低
[[Java HashTable]]
2.使用Collections类的 synchronizedMap 方法包装一下
(不是绝对的线程安全)
[[Java synchronizedMap]]
3.使用ConcurrentHashMap
它使用分段锁来保证线程安全
通过前两种方式获得的线程安全的HashMap在读写数据的时候会对整个容器上锁,而ConcurrentHashMap并不需要对整个容器上锁,它只需要锁住要修改的部分就行了
[[Java ConcurrentHashMap]]
Java HashTable
HashTable 中所有 CRUD 操作
都是线程同步的,同样的,线程同步的代价就是效率变低了。
所有的读写操作都进行了锁保护,是线程安全的。
Hashtable 中的常用变量
privatetransientEntry<?,?>[] table;//底层保存节点的数组privatetransientint count;//记录表中的节点数privateint threshold;//阈值,当表的节点数>=该阈值,会进行扩容privatefloat loadFactor;//负载因子,计算阈值用的privatetransientint modCount =0;//Hashtable在结构上被修改的次数// 计算节点在数组的下标int index =(hash &0x7FFFFFFF)% tab.length;
HashTable 中的常用方法
构造方法
contains()
publicsynchronizedbooleancontainsKey(Object key){Entry<?,?> tab[]= table;//表int hash = key.hashCode();//计算key在数组中的下标int index =(hash &0x7FFFFFFF)% tab.length;//遍历链表for(Entry<?,?> e = tab[index]; e !=null; e = e.next){if((e.hash == hash)&& e.key.equals(key)){returntrue;}}returnfalse;}
Java synchronizedMap
方法如下:
// 返回由指定映射支持的同步(线程安全的)映射publicstatic<K,V>Map<K,V>synchronizedMap(Map<K,V> m)privatestaticMap<String,List<MaxMinTimeValue>> maxMinTimeValueMap =Collections.synchronizedMap(newLinkedHashMap<String,List<MaxMinTimeValue>>());privatestaticMap<String,MaxMinTimeValue> maxMinTimeValueMap4Kuashiduan =Collections.synchronizedMap(newLinkedHashMap<String,MaxMinTimeValue>());
它是将map的每个方法都加了同步(都加了虚拟锁机制)
但是他们放在一起操作(多个该map(map是通过Collections.synchronizedMap来定义的)的方法)还是会出现线程不安全的事情。如当程序正在执行containsKey方法时,另一个程序已经将key删除了。会出现这种情况。就是会有两个线程同时操作map,导致不可预见性的结果。这时为了解决此问题,还是要加上同步机制。如:
privatestaticfinalObject object=newObject();publicvoidremoveKey(String key){synchronized(object){if(map.containsKey(key)){//containsKey方法是线程安全的方法
map.remove(key);//remove方法也是线程安全的方法。}}}
故 Collections.synchronizedMap 也只是有条件的线程安全的。而不是绝对的线程安全的。还是少用的好。
Java ConcurrentHashMap
简介
再 Java 1.5 版本引入 ConcurrentHashMap,实现线程安全。
ConcurrentHashMap 将 hash 表分为 16 个桶(默认值),诸如 get,put,remove 等常
用操作只锁当前需要用到的桶。试想,原来只能一个线程进入,现在却能同时 16 个写线程进入(写线程
才需要锁定,而读线程几乎不受限制,并发性的提升是显而易见。
ConcurrentHashMap是一个哈希表,支持检索的全并发和更新的高预期并发。此类遵循与 Hashtable 相同的功能规范,并包含 Hashtable 的所有方法。ConcurrentHashMap 位于 java.util.Concurrent 包中。
- java.util.Concurrent.ConcurrentHashMap类通过将map划分为segment来实现线程安全,不是整个对象需要锁,而是一个segment,即一个线程需要一个segment的锁。
- 在 ConcurrenHashap 中,读操作不需要任何锁。
当一个线程迭代 Map 对象时,其他线程被允许修改 Map,我们不会得到 ConcurrentModificationException
键和值都不允许为 Null。
不同版本的 ConcurrentHashMap 的扩容
JDK 1.7 版本
略……
JDK 1.8 版本
- 1.8版本的 ConcurrentHashMap 不再基于 Segment 实现。
- 当某个线程进行 put 时,如果发现 ConcurrentHashMap 正在进行扩容那么该线程一起进行扩容。
- 当某个线程进行 put 时,如果没有正在进行扩容,则将 key-value 添加到 ConcurrentHashMap 中,然后判断是否超过阀值,超过了则进行扩容。
- ConcurrentHashMap 是支持多个线程同时扩容的。
- 扩容之前也先生成一个新的数组。
- 在转移元素时,先将原数组分组,将每组分给不同的线程来进行元素的转移,每个线程负责一组或多组的元素转移工作。
底层实现
JDK 1.7 版本
- 底层是数组 + 链表
JDK 1.8 版本
- 底层是数组 + 链表 + 红黑树,加红黑树的目的是提高 HashMap 插入和查询整体效率。
- 链表插入使用的是尾插法,因为 1.8 中插入 key 和 value 时需要判断链接元素个数,所以需要遍历链表统计链表元素个数,所以正好就直接使用尾插法。
- 1.8 中简化了 Hash 算法,因为复杂的哈希算法目的就是提高散列性,来提供 HashMap 的整体效率,而 1.8 中新增了红黑树,所以可以适当的简化哈希算法,节省 CPU 资源。
版权归原作者 wide288 所有, 如有侵权,请联系我们删除。