非线程安全的HashMap 和 线程安全的ConcurrentHashMap
在平时开发中,我们经常采用HashMap来作为本地缓存的一种实现方式,将一些如系统变量等数据量比较少的参数保存在HashMap中,并将其作为单例类的一个属性。在系统运行中,使用到这些缓存数据,都可以直接从该单例中获取该属性集合。但是,最近发现,HashMap并不是线程安全的,如果你的单例类没有做代码同步或对象锁的控制,就可能出现异常。
首先看下在多线程的访问下,非现场安全的HashMap的表现如何,在网上看了一些资料,自己也做了一下测试:
public class MainClass {2
3
public static final HashMap<String, String> firstHashMap=new HashMap<String, String>();4
5
public static void main(String[] args) throws InterruptedException {6
7
//线程一8
Thread t1=new Thread(){9
public void run() {;i++){
firstHashMap.put(String.valueOf(i), String.valueOf(i));
}
}
};
//线程二
Thread t2=new Thread(){
public void run() {;j++){
firstHashMap.put(String.valueOf(j), String.valueOf(j));
}
}
};
t1.start();
t2.start();
//主线程休眠1秒钟,以便t1和t2两个线程将firstHashMap填装完毕。);
;l++){
//如果key和value不同,说明在两个线程put的过程中出现异常。
if(!String.valueOf(l).equals(firstHashMap.get(String.valueOf(l)))){
System.err.println(String.valueOf(l)+":"+firstHashMap.get(String.valueOf(l)));
}
}
}
}上面的代码在多次执行后,发现表现很不稳定,有时没有异常文案打出,有时则有个异常出现:
为什么会出现这种情况,主要看下HashMap的实现:
public V put(K key, V value) {2
if (key == null)3
return putForNullKey(value);4
int hash = hash(key.hashCode());5
int i = indexFor(hash, table.length);6
for (Entry<K,V> e = table[i]; e != null; e = e.next) {7
Object k;8
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {9
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}我觉得问题主要出现在方法addEntry,继续看:
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)* table.length);
}从代码中,可以看到,如果发现哈希表的大小超过阀值threshold,就会调用resize方法,扩大容量为原来的两倍,而扩大容量的做法是新建一个Entry[]:
void resize(int newCapacity) {2
Entry[] oldTable = table;3
int oldCapacity = oldTable.length;4
if (oldCapacity == MAXIMUM_CAPACITY) {5
threshold = Integer.MAX_VALUE;6
return;7
}8

9
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}一般我们声明HashMap时,使用的都是默认的构造方法:HashMap<K,V>,看了代码你会发现,它还有其它的构造方法:HashMap(intinitialCapacity, float loadFactor),其中参数initialCapacity为初始容量,loadFactor为加载因子,而之前我们看到的threshold
= (int)(capacity* loadFactor);如果在默认情况下,一个HashMap的容量为16,加载因子为0.75,那么阀值就是12,所以在往HashMap中put的值到达12时,它将自动扩容两倍,如果两个线程同时遇到HashMap的大小达到12的倍数时,就很有可能会出现在将oldTable转移到newTable的过程中遇到问题,从而导致最终的HashMap的值存储异常。
JDK1.0引入了第一个关联的集合类HashTable,它是线程安全的。 HashTable的所有方法都是同步的。
JDK2.0引入了HashMap,它提供了一个不同步的基类和一个同步的包装器synchronizedMap。synchronizedMap被称为有条件的线程安全类。
JDK5.0util.concurrent包中引入对Map线程安全的实现ConcurrentHashMap,比起synchronizedMap,它提供了更高的灵活性。同时进行的读和写操作都可以并发地执行。
所以在开始的测试中,如果我们采用ConcurrentHashMap,它的表现就很稳定,所以以后如果使用Map实现本地缓存,为了提高并发时的稳定性,还是建议使用ConcurrentHashMap。
====================================================================
另外,还有一个我们经常使用的ArrayList也是非线程安全的,网上看到的有一个解释是这样:
一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2.增大 Size 的值。
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B得到运行的机会。线程B也将元素放在位置0,(因为size还未增长),完了之后,两个线程都是size++,结果size变成2,而只有items[0]有元素。
util.concurrent包也提供了一个线程安全的ArrayList替代者CopyOnWriteArrayList。
本文转载自:http://www.blogjava.net/lukangping/articles/331089.html
非线程安全的HashMap 和 线程安全的ConcurrentHashMap的更多相关文章
- 如何使用线程安全的HashMap
转载:https://blog.csdn.net/qq_31493821/article/details/78855069 HashMap为什么线程不安全 导致HashMap线程不安全的原因可能有两种 ...
- Java 非线程安全的HashMap如何在多线程中使用
Java 非线程安全的HashMap如何在多线程中使用 HashMap 是非线程安全的.在多线程条件下,容易导致死循环,具体表现为CPU使用率100%.因此多线程环境下保证 HashMap 的线程安全 ...
- HashMap为什么线程不安全(hash碰撞与扩容导致)
一直以来都知道HashMap是线程不安全的,但是到底为什么线程不安全,在多线程操作情况下什么时候线程不安全? 让我们先来了解一下HashMap的底层存储结构,HashMap底层是一个Entry数组,一 ...
- HashMap变成线程安全方法
我们都知道.HashMap是非线程安全的(非同步的).那么怎么才能让HashMap变成线程安全的呢? 我认为主要可以通过以下三种方法来实现: 1.替换成Hashtable,Hashtable通过对整个 ...
- 为什么说 HashMap 是非线程安全的?
我们在学习 HashMap 的时候,都知道 HashMap 是非线程安全的,同时我们知道 HashTable 是线程安全的,因为里面的方法使用了 synchronized 进行同步. 但是 HashM ...
- Java基础知识强化之集合框架笔记80:HashMap的线程不安全性的体现
1. HashMap 的线程不安全性的体现: 主要是下面两方面: (1)多线程环境下,多个线程同时resize()时候,容易产生死锁现象.即:resize死循环 (2)如果在使用迭代器的过程中有其他线 ...
- 浅谈HashMap与线程安全 (JDK1.8)
HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型.HashMap 继承自 AbstractMap 是基于哈希表的 Map 接口的实现,以 Key-Value 的形式存在,即 ...
- [集合]线程安全的HashMap
一.一般模式下线程安全的HashMap 默认情况常用的HashMap都是线程不安全的,在多线程的环境下使用,常常会造成不可预知的,莫名其妙的错误.那么,我们如何实现一个线程安全的HashMap呢?其中 ...
- hashMap的线程不安全
hashMap是非线程安全的,表现在两种情况下: 1 扩容: t1线程对map进行扩容,此时t2线程来读取数据,原本要读取位置为2的元素,扩容后此元素位置未必是2,则出现读取错误数据. 2 hash碰 ...
随机推荐
- VS2019企业版产品密钥
Visual Studio 2019 Enterprise产品密钥(激活码) BF8Y8-GN2QH-T84XB-QVY3B-RC4DF
- UVALive 3983 捡垃圾的机器人 DP
这个题目我最初的做法沿用树形DP的做法,设置一个 dp[i][0]表示机器人在i点不回去的最短路径,dp[i][1]表示机器人在i点回去的最短路径,规划方向为i-1向i转移,结果发现这个不能用树形的结 ...
- 2020.2.21一些python总结
#字符串前面加r可以防止字符串转义 \也可以防止字符串转义#三引号可以实现输入多行文本#range(start,end,step)#列表 append添加一个元素到末尾 extend 添加一个素组到末 ...
- 题解 P2622 【关灯问题II】
题目 感觉大佬们的代码在读入上的处理比本蒟蒻优秀多了,于是,一个AFO蒟蒻弱弱地提出一下自己的看法 [分析] 首先,对于 \(n\) 那么小,肯定是状压啦 对于读入,本蒟蒻开了两个数组来储存每个按钮的 ...
- 斐波那契数列 yield 和list 生成
def fab_demo4(max): a,n,b = 0,0,1 while n < max: yield b # 生成器走到这一步返回b,需要再次调用才能继续执行 a,b = b,a+b n ...
- opencv摄像头捕获视频
1.ord()函数:它以一个字符(长度为1的字符串)作为参数,返回对应的 ASCII 数值,或者 Unicode 数值,如果所给的 Unicode 字符超出了你的 Python 定义范围,则会引发一个 ...
- shell中获取文件目录方法
1.``:表示执行对应的命令,嵌套时使用`\`\``,注意\进行转义,同时执行多个命令时使用:隔开file=`cd "\`dirname $0\`";pwd`echo $file ...
- What is the maximum length of a URL in different browsers?
https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers ...
- 产品对话 | 愿云原生不再只有Kubernete
从2013年,云原生(Cloud Native)的概念由 Pivotal 的 MattStine 首次提出,到现在,其技术细节不断得到社区的完善.云原生逐渐演变出包括 DevOps.持续交付.微服务. ...
- MyBatis学习——动态SQL
开发人员在使用JDBC框架或者其他类似的框架进行数据库开发时,通常都要根据需求去手动拼接SQL,这样非常麻烦,而myBatis提供了对SQL语句动态组装的功能,恰好解决了这一问题. 一,动态SQL中的 ...