Java并发编程中的HashMap、HashTable、ConcurrentHashMap

Java并发编程中的HashMap、HashTable、ConcurrentHashMap_第1张图片

1、HashMap

1.1、为什么HashMap非线程安全的

(1)竞态条件

  • 当多个线程同时对 HashMap 进行写操作(如插入、删除、修改),由于没有同步控制,可能会导致数据不一致的情况。
  • 例如,两个线程同时向同一个空的 HashMap 插入不同的键值对,由于没有互斥操作,它们可能会同时触发扩容操作,导致其中一个线程的插入操作被覆盖或丢失。

(2)死循环(JDK1.8 之前)

  • 在多线程环境下,如果一个线程正在进行 HashMap 的结构调整(如扩容),而另一个线程正在进行读取或修改操作,可能会导致读取或修改操作陷入死循环。
  • 这是因为在结构调整期间,HashMap 的链表结构可能会发生变化,而另一个线程可能无法正确地遍历或定位到元素。
死循环示例
  • 前置知识
    Java并发编程中的HashMap、HashTable、ConcurrentHashMap_第2张图片
  • 死循环执行步骤

Java并发编程中的HashMap、HashTable、ConcurrentHashMap_第3张图片

(3)JDK1.8 开始便没有了死循环问题

  • JDK8 将 HashMap 的结构作了下修改,将原来的链表部分改为数据少时仍然链表,当超过一定数量后变换为红黑树。
  • 红黑树:一种自平衡的二叉搜索树(Binary Search Tree),它在每个节点上增加了一个额外的属性来维持平衡。红黑树的平衡性质使得它在插入、删除和查找等操作的时间复杂度上具有较好的保证,为O(log n)。

通过上面的分析,不难发现循环的产生是因为新链表的顺序跟旧的链表是完全相反的,所以只要保证建新链时还是按照原来的顺序的话就不会产生循环。

  • JDK8是用 head 和 tail 来保证链表的顺序和之前一样,这样就不会产生循环引用。

1.2、保证HashMap线程安全的方案

  • 【方案1】使用Hashtable线程安全类
  • 【方案2】使用Collections.synchronizedMap方法,对方法进行加同步锁
  • 【方案3】使用并发包中的ConcurrentHashMap类

2、HashTable

2.1、HashTable性能问题

  • Hashtable 是一个线程安全的类,Hashtable 几乎所有的添加、删除、查询方法都加了synchronized同步锁!
  • 相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞等待需要的锁被释放,在竞争激烈的多线程场景中性能就会非常差,所以 Hashtable 不推荐使用!

2.2、Collections.synchronizedMap性能问题

  • Collections.synchronizedMap 里面使用对象锁来保证多线程场景下,操作安全,本质也是对 HashMap 进行全表锁!
  • 使用Collections.synchronizedMap方法,在竞争激烈的多线程环境下性能依然也非常差,所以不推荐使用!

3、ConcurrentHashMap

Java并发编程中的HashMap、HashTable、ConcurrentHashMap_第4张图片

CHM是弱一致性的

  • 添加元素后,不一定马上读到
  • 清空之后,可能仍然会有元素
  • 遍历之前的变化,可以读到
  • 遍历之后的变化,无法读到
  • 遍历时元素发生变化,不抛异常

Java并发编程中的HashMap、HashTable、ConcurrentHashMap_第5张图片

3.1、JDK1.5、JDK1.6分段锁

Java并发编程中的HashMap、HashTable、ConcurrentHashMap_第6张图片

  • JDK1.5对key进行hash,然后用hash值的高位查找segment[],低位则查找table[]
  • JDK1.6的优化是得到的hash值高位和低位在segment[]和table[]分布更均匀
  • 分16段,所以在理想状态下,ConcurrentHashMap 可以支持 16 个线程执行并发写操作(如果并发级别设为16),及任意数量线程的读操作

3.2、JDK1.7分段锁懒加载

Java并发编程中的HashMap、HashTable、ConcurrentHashMap_第7张图片

  • JDK1.5、JDK1.6是直接初始化16个segment
  • JDK1.7则是用到哪个segment再初始化哪个,没用到的不初始化
  • JDK1.7的segment使用volatile修饰

3.3、JDK1.8摒弃分段锁

Java并发编程中的HashMap、HashTable、ConcurrentHashMap_第8张图片

  • volatile直接修饰table
  • 加锁也直接加到table上

为什么HashMap会产生死循环

多线程下的HashMap死循环问题详解

彻头彻尾理解 ConcurrentHashMa

一文彻底弄懂ConcurrentHashMap

ConcurrentHashMap

你可能感兴趣的:(Android,Learning,java,并发编程,多线程)