0


Java HashMap 实现线程安全

非线程安全。(非同步)

怎么才能让 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 资源。

本文转载自: https://blog.csdn.net/wide288/article/details/127862145
版权归原作者 wide288 所有, 如有侵权,请联系我们删除。

“Java HashMap 实现线程安全”的评论:

还没有评论