线程安全集合类 ConcurrentHashMap

HashMap 在多线程的执行过程当中,出现线程安全的问题,如何解决?

1、使用 HashTable

几乎在每一个方法中添加了 synchornized 关键字,是一种性能低下的线程安全集合类;

为什么 HashTable 不允许使用 null 作为 key? 但是HashMap 可以的?

主要是因为为了避免程序执行的二义性:
1、不清楚是原来的 key 不存在
2、还是存储的 key 就是 null;

HashMap ConcurrentHashMap 都是存在二义性的,但是 HashMap 通过结合 contains(key) 的使用可以消除这种二义性;

但是 ConcurrentHashMap ,在多线程的访问之下,是没有办法判断到底是本身 key 不存在还是原来的 key 就是 null;

因为 ConcurrentHashMap 使用的是 fail - safe 机制,获取到的数据不是最新的,所以不能使用 contains(key) 进行例如 HashMap 的判断;

ConcurrentHashMap的key value不能为null,map可以?

2、使用 synchronizedMap

synchronizedMap 如何实现线程安全的?

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;
}

在 synchornizedMap 中的源代码中存在 Map m 变量,mutex 互斥锁,当使用无参数构造的时候,互斥锁就是传入进来的 Map m 对象;

使用有参数构造函数的时候,使用传递进来的对象作为互斥锁;

有了互斥锁之后,在底层源代码中,对相关的方法代码使用 synchornized 进行加锁控制线程安全的问题;

所谓的互斥锁 mutex ,就是同一时间中,只能有一个线程可以访问这个mutex 对象,其他对象也想要访问 mutex 的时候,在外面等待当前线程完成操作之后释放了锁,才能继续访问;

// 截取的部分代码,大量的使用 synchornized 进行加锁,控制并发访问;防止出现线程安全问题;
public int size() {
    synchronized (mutex) {return m.size();}
}
public boolean isEmpty() {
    synchronized (mutex) {return m.isEmpty();}
}
public boolean containsKey(Object key) {
    synchronized (mutex) {return m.containsKey(key);}
}
public boolean containsValue(Object value) {
    synchronized (mutex) {return m.containsValue(value);}
}
public V get(Object key) {
    synchronized (mutex) {return m.get(key);}
}

public V put(K key, V value) {
    synchronized (mutex) {return m.put(key, value);}
}
public V remove(Object key) {
    synchronized (mutex) {return m.remove(key);}
}
public void putAll(Map<? extends K, ? extends V> map) {
    synchronized (mutex) {m.putAll(map);}
}
public void clear() {
    synchronized (mutex) {m.clear();}
}

3、使用效率高得 ConcurrentHashMap

详细的原理在下文中进行相关的描述;

JDK 1.7 HashMap 在多线程下面形成的死链问题

参考下面的博客,写的十分的清楚
为什么HashMap 1.7 会产生死循环?

小结一下:主要的原因就是两个线程同时想要对 HashMap 1.7 进行扩容,但是扩容的操作没有同步;

有一个线程已经完成了扩容,哈希桶上面的元素的引用已经变了;

但是,另外一个线程同样还是按照原来的引用进行扩容,造成了死链的形成;

扩容本来是遍历所有元素,放置到新的哈希表中,但是遍历不下去了,因为形成了死链。一直在死链上面循环,得不到放置到新的哈希表里面的元素,一直僵持;

ConcurrentHashMap 1.7 的实现原理

图解ConcurrentHashMap 1.7 的实现

ConcurrentHashMap 1.8 的实现原理

Fail - Fast Fail - Safe 机制

Fail - Fast

Fail - Fast机制就是指的使用迭代器遍历集合的时候,如果集合的结构发生了变化,会出现异常; 结构出现的异常是:增加或者删除元素的时候,集合的结构就发生了变化;

  • fail-fast 机制是Java集合(Collection)中的一种错误机制。 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的结构进行了修改(增加、删除),则会抛出ConcurrentModificationException(并发修改异常)。

  • 所以,在多线程环境下,是很容易抛出ConcurrentModificationException的,比如线程1正在对集合进行遍历,此时线程2对集合进行修改(增加、删除)。但是,单线程就不会抛出吗?很明显,单线程也会有类似的情况,比如main线程在遍历时对集合进行修改(增加、删除、修改),那么main线程就会抛出ConcurrentModification Exception异常

  • 阿里的技术手册中禁止在迭代器中使用 ArrayList 的 add() 方法

ArrayList 在遍历的时候,ArrayList 的结构是不允许改变的,否则会出现ConcurrentModificationException,底层就是 迭代器的 expectedModCount 与 ArrayList 的 modCount 数值不一致,所以会报错,所以在使用迭代器进行遍历的时候,禁止使用 add() 方法;

或者说在迭代器中禁止使用所有有修改 modCount 的方法,因为会出现ConcurrentModificationException;

但是有的方法中没有修改 modCount 就是可以放置在迭代器中使用的;

Fail - Safe

采用安全失败机制的集合容器,使用迭代器遍历的是原来集合的拷贝版本,不在原来的集合中进行遍历;

迭代器在遍历的时候访问的是拷贝的集合,所以在遍历的过程中对于集合结构的修改不能被迭代器检测到,所以就触发不了 ConcurrentModificationException 异常;

优点:
避免了ConcurrentModificationException异常;

缺点:
1、拷贝的时候产生了大量的对象,对于系统资源的开销是比较大的;
2、在使用迭代器遍历集合的时候,没有办法保证得到的数据是最新的,因为可能在集合拷贝之后,原始的集合进行了数据的更新;

参考博客

为什么HashMap 1.7 会产生死循环?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中,有几个线程安全集合类可以用于多线程环境下的操作。其中包括Vector、Stack、HashTable等。这些集合类通过使用Synchronized来保证线程安全性。不过需要注意的是,官方已经标注Vector和HashTable为即将废弃的类,不建议使用它们。 除了上述的线程安全集合类,还有其他一些较新的线程安全集合类可以使用。其中包括CopyOnWriteArrayList、CopyOnWriteArraySet和ConcurrentHashMap等。这些集合类通过特定的机制来实现线程安全性,而且在性能方面也进行了优化。 此外,Java中还有Queue接口,它包含了几个线程安全的实现类,如ConcurrentLinkedQueue和BlockingQueue接口的实现类。这些集合类可以在多线程环境下进行安全的队列操作。 总之,在Java中有多种线程安全集合类可供选择,可以根据具体的需求和场景选择合适的集合类来保证线程安全性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Java中那些线程安全集合类](https://blog.csdn.net/weixin_53946852/article/details/122801143)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Java线程安全的集合](https://blog.csdn.net/weixin_42601136/article/details/107108818)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值