转载:https://blog.csdn.net/qq_31493821/article/details/78855069

HashMap为什么线程不安全

导致HashMap线程不安全的原因可能有两种:

1、当多个线程同时使用put方法添加元素的时候,正巧存在两个put的key发生了碰撞(根据hash值计算的bucket一样),那么根据HashMap的存储原理,这两个key会添加多数组的同一个位置,这样一定会导致其中一个线程put的数据被覆盖丢失

2、当多个线程同时检测到元素个数超过哈希表的size*loadFloat的时候,这样会发生多个线程同时对Node数组进行扩容的操作(java1.8中HashMap使用Node实体来存放内容),都在重新计算元素位置以及拷贝数据,但最后只能有一个线程能成功的将扩容后的数组赋值给table,也就是说其他线程的都会丢失,并且各自线程的数据也会丢失。关于hashMap线程不安全这一点,《java并发编程的艺术》一书中是这么描述的:“hashMap在并发执行put操作会引起死循环,导致CPU利用率接近100%。因为多线程会导致HashMap的Node链表形成环数据结构,一旦形成环数据结构,Node的next节点永远不为空,就会在获取Node时产生死循环”。由此可知,HashMap的死循环是发生在扩容时。关于hashMap的死循环可参看一下文章:

hashMap在java并发中如何发生死循环
How dose a Hashmap work in java

何如使用线程安全的HashMap
实现线程安全的方式有三种,分别是使用HashTable、Collections.SynchronizeMap、ConcurrentHashMap。

我们先分别来看看三种数据结构的部分源码:

hashtable

public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; } publicsynchronizedV remove(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { modCount++; if (prev != null) { prev.next = e.next; } else { tab[index] = e.next; } count--; V oldValue = e.value; e.value = null; return oldValue; } } return null; }
SynchronizeMap

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();}
}
通过这部分源码可知,HashTable、Collections.SynchronizeMap都是通过对读写进行加锁操作来保证线程的安全,一个线程进行读写数据,其余线程等等,可想而知,性能可想而知。

ConcurrentHashMap

在java8中,ConcurrentHashMap摒弃了Segment,启用了一种新的算法实现CAS。关于ConcurrentHashMap工作机制请参考深入浅出ConcurrentHashMap1.8

下面我们通过一些代码来验证他们之前的线程安全以及效率问题:

package com.iresearch.idata.common.util.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;

/**
* @author: htc
* @date: Created in 18:18 2017/12/20.
*/
public class ConcurrentMapWithMap {
private static Map<String, Long> mapWordCounts = new HashMap<>();
private static ConcurrentMap<String, Long> concurrentMapWordCounts = new ConcurrentHashMap<>();
public static Logger logger = LoggerFactory.getLogger(ConcurrentMapWithMap.class);
public static int count = 0;

public static long mapIncrease(String word) {
Long oldValue = mapWordCounts.get(word);
Long newValue = (oldValue == null) ? 1L : oldValue + 1;
mapWordCounts.put(word, newValue);
return newValue;
}

public static long ConcurrentMapIncrease(String word) {
Long oldValue, newValue;
while (true) {
oldValue = concurrentMapWordCounts.get(word);
if (oldValue == null) {
// Add the word firstly, initial the value as 1
newValue = 1L;
if (concurrentMapWordCounts.putIfAbsent(word, newValue) == null) {
break;
}
} else {
newValue = oldValue + 1;
if (concurrentMapWordCounts.replace(word, oldValue, newValue)) {
break;
}
}
}
return newValue;
}

public static void mapWordCount() throws InterruptedException, ExecutionException {
new Thread(new Runnable() {
@Override
public void run() {
int count = 0;
while (count++ < 10000) {
logger.info("mapIncrease num is " + ConcurrentMapWithMap.mapIncrease("work"));
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
int count = 0;
while (count++ < 10000) {
logger.info("mapIncrease num is " + ConcurrentMapWithMap.mapIncrease("work"));
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
int count = 0;
while (count++ < 10000) {
logger.info("mapIncrease num is " + ConcurrentMapWithMap.mapIncrease("work"));
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
int count = 0;
while (count++ < 10000) {
logger.info("mapIncrease num is " + ConcurrentMapWithMap.mapIncrease("work"));
}
}
}).start();
}

public static void concurrentWordCount() throws InterruptedException, ExecutionException {
new Thread(new Runnable() {
@Override
public void run() {
int count = 0;
while (count++ < 10000) {
logger.info("mapIncrease num is " + ConcurrentMapWithMap.ConcurrentMapIncrease("work"));
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
int count = 0;
while (count++ < 10000) {
logger.info("mapIncrease num is " + ConcurrentMapWithMap.ConcurrentMapIncrease("work"));
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
int count = 0;
while (count++ < 10000) {
logger.info("mapIncrease num is " + ConcurrentMapWithMap.ConcurrentMapIncrease("work"));
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
int count = 0;
while (count++ < 10000) {
logger.info("mapIncrease num is " + ConcurrentMapWithMap.ConcurrentMapIncrease("work"));
}
}
}).start();
}

public static void main(String[] args) throws InterruptedException, ExecutionException {
ConcurrentMapWithMap.mapWordCount();
Thread.sleep(10000);
//多线程累加,每次都少于40000,故线程不安全
logger.info("final count map" + ConcurrentMapWithMap.mapWordCounts.get("work"));
ConcurrentMapWithMap.concurrentWordCount();
Thread.sleep(10000);
//多线程累加,每次都是40000
logger.info("final count concurrentMap" + ConcurrentMapWithMap.concurrentMapWordCounts.get("work"));
}
}

---------------------
作者:专属_Smile
来源:CSDN
原文:https://blog.csdn.net/qq_31493821/article/details/78855069
版权声明:本文为博主原创文章,转载请附上博文链接!

如何使用线程安全的HashMap的更多相关文章

  1. Java 非线程安全的HashMap如何在多线程中使用

    Java 非线程安全的HashMap如何在多线程中使用 HashMap 是非线程安全的.在多线程条件下,容易导致死循环,具体表现为CPU使用率100%.因此多线程环境下保证 HashMap 的线程安全 ...

  2. [集合]线程安全的HashMap

    一.一般模式下线程安全的HashMap 默认情况常用的HashMap都是线程不安全的,在多线程的环境下使用,常常会造成不可预知的,莫名其妙的错误.那么,我们如何实现一个线程安全的HashMap呢?其中 ...

  3. 非线程安全的HashMap 和 线程安全的ConcurrentHashMap

    在平时开发中,我们经常采用HashMap来作为本地缓存的一种实现方式,将一些如系统变量等数据量比较少的参数保存在HashMap中,并将其作为单例类的一个属性.在系统运行中,使用到这些缓存数据,都可以直 ...

  4. 3.3.2线程安全的HashMap

    代码:public class SysHashMao { private static Map<String,String> map= Collections.synchronizedMa ...

  5. 问题(一)---线程池,锁、堆栈和Hashmap相关

    一.线程池: 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力. 假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中 ...

  6. HashMap、HashTable、ConcurrentHashMap、HashSet区别 线程安全类

    HashMap专题:HashMap的实现原理--链表散列 HashTable专题:Hashtable数据存储结构-遍历规则,Hash类型的复杂度为啥都是O(1)-源码分析 Hash,Tree数据结构时 ...

  7. Java基础知识强化之集合框架笔记80:HashMap的线程不安全性的体现

    1. HashMap 的线程不安全性的体现: 主要是下面两方面: (1)多线程环境下,多个线程同时resize()时候,容易产生死锁现象.即:resize死循环 (2)如果在使用迭代器的过程中有其他线 ...

  8. 浅谈HashMap与线程安全 (JDK1.8)

    HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型.HashMap 继承自 AbstractMap 是基于哈希表的 Map 接口的实现,以 Key-Value 的形式存在,即 ...

  9. HashMap和Hashtable 线程安全性

    HashMap和Hashtable的比较是Java面试中的常见问题,用来考验程序员是否能够正确使用集合类以及是否可以随机应变使用多种思路解决问题.HashMap的工作原理.ArrayList与Vect ...

随机推荐

  1. TortoiseSVN客户端安装遇到的问题汇总

    在windows server 2003版本上安装32位SVN客户,提示以下错误 1:无法通过windows installer服务安装此安装程序包” 这时需要安装更新的windows install ...

  2. 关于MySQL隐式转换

    一.如果表定义的是varchar字段,传入的是数字,则会发生隐式转换. 1.表DDL 2.传int的sql 3.传字符串的sql 仔细看下表结构,rid的字段类型: 而用户传入的是int,这里会有一个 ...

  3. Spark的广播变量模块

    有人问我,如果让我设计广播变量该怎么设计,我想了想说,为啥不用zookeeper呢? 对啊,为啥不用zookeeper,也许spark的最初设计哲学就是尽量不使用别的组件,他有自己分布式内存文件系统, ...

  4. C/C++中struct/union/class内存对齐

    struct/union/class内存对齐原则有四个: 1).数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储 ...

  5. Vistual Studio的导出模板功能

    应用场景,每个项目有自己固定的目录结构和引用文件, 无需每次创建一个项目,就手工一一将那些目录再new一遍.如图所示 菜单  文件=>导出模板 之后的操作基本上一路"下一步" ...

  6. oracle sql 字段行转列

    数据库中原先如图: 现在要吧WHMM行转列: conncect by用于确定列的个数

  7. [SinGuLaRiTy] 图论题目复习

    [SInGuLaRiTy-1027] Copyright (c) SinGuLaRiTy 2017. All Rights Reserved. [Vijos 1423] 最佳路线 题目描述 年久失修的 ...

  8. python web框架之Tornado的简单使用

    python web框架有很多,比如常用的有django,flask等.今天主要介绍Tornado ,Tornado是一个用Python写的相对简单的.不设障碍的Web服务器架构,用以处理上万的同时的 ...

  9. P4768 [NOI2018]归程

    \(\color{#0066ff}{题目描述}\) 本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定. 魔力之都可以抽象成一个 n 个节点.m 条边的无向连通图(节点的编号从 1 至 n) ...

  10. 最短路+状压DP【洛谷P3489】 [POI2009]WIE-Hexer

    P3489 [POI2009]WIE-Hexer 大陆上有n个村庄,m条双向道路,p种怪物,k个铁匠,每个铁匠会居住在一个村庄里,你到了那个村庄后可以让他给你打造剑,每个铁匠打造的剑都可以对付一些特定 ...