在平时开发中,我们经常采用HashMap来作为本地缓存的一种实现方式,将一些如系统变量等数据量比较少的参数保存在HashMap中,并将其作 为单例类的一个属性。在系统运行中,使用到这些缓存数据,都可以直接从该单例中获取该属性集合。但是,最近发现,HashMap并不是线程安全的,如果你 的单例类没有做代码同步或对象锁的控制,就可能出现异常。
首先看下在多线程的访问下,非现场安全的HashMap的表现如何,在网上看了一些资料,自己也做了一下测试:
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() {
10 for ( int i = 0 ;i < 25 ;i ++ ) {
11 firstHashMap.put(String.valueOf(i), String.valueOf(i));
12 }
13 }
14 } ;
15
16 // 线程二
17 Thread t2 = new Thread() {
18 public void run() {
19 for ( int j = 25 ;j < 50 ;j ++ ) {
20 firstHashMap.put(String.valueOf(j), String.valueOf(j));
21 }
22 }
23 } ;
24
25 t1.start();
26 t2.start();
27
28 // 主线程休眠1秒钟,以便t1和t2两个线程将firstHashMap填装完毕。
29 Thread.currentThread().sleep( 1000 );
30
31 for ( int l = 0 ;l < 50 ;l ++ ) {
32 // 如果key和value不同,说明在两个线程put的过程中出现异常。
33 if ( ! String.valueOf(l).equals(firstHashMap.get(String.valueOf(l)))) {
34 System.err.println(String.valueOf(l) + " : " + firstHashMap.get(String.valueOf(l)));
35 }
36 }
37
38 }
39
40 }
上面的代码在多次执行后,发现表现很不稳定,有时没有异常文案打出,有时则有个异常出现:
为什么会出现这种情况,主要看下HashMap的实现:
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;
10 e.value = value;
11 e.recordAccess( this );
12 return oldValue;
13 }
14 }
15
16 modCount ++ ;
17 addEntry(hash, key, value, i);
18 return null ;
19 }
我觉得问题主要出现在方法addEntry,继续看:
2 Entry < K,V > e = table[bucketIndex];
3 table[bucketIndex] = new Entry < K,V > (hash, key, value, e);
4 if (size ++ >= threshold)
5 resize( 2 * table.length);
6 }
从代码中,可以看到,如果发现哈希表的大小超过阀值threshold,就会调用resize方法,扩大容量为原来的两倍,而扩大容量的做法是新建一个Entry[]:
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];
10 transfer(newTable);
11 table = newTable;
12 threshold = ( int )(newCapacity * loadFactor);
13 }
一般我们声明HashMap时,使用的都是默认的构造方法:HashMap<K,V>,看了代码你会发现,它还有其它的构造方法:HashMap(int initialCapacity, 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。
相关推荐
HashMap为什么是线程不安全的?如何解决HashMap的线程不安全问题?
高级程序员必会的HashMap的线程安全问题,适用于0~2年的
经常会看到说HashMap是线程不安全的,ConcurrentHashMap是线程安全的等等说法,不禁有个疑问,什么是线程安全?什么样的类是线程安全的? 1.什么是线程安全性(what) 线程安全定义,最核心是正确性, 正确性:多个...
NULL 博文链接:https://flyfoxs.iteye.com/blog/1198030
哈希映射线程测试使用 Maven 构建和运行 mvn exec:java
这就有可能导致A线程和B线程同时对一个数组扩容,A线程扩容后替换掉老数组,这时B线程使用的数组实际上是A线程扩容后的数组,就会产生线程安全问题。 死锁原因 比如,当前集合数组长度为2,已经有两个元素被放在了...
首先介绍一下什么是Map。在数组中我们是通过数组下标来对其内容... HashMap 非线程安全 TreeMap 非线程安全 线程安全 在Java里,线程安全一般体现在两个方面: 1、多个thread对同一个java实例的访问(read和
HashMap和HashTable的区别?但是如果想线程安全有想效率高?
Java集合多线程安全 线程安全与不安全集合 线程不安全集合: ArrayList LinkedList HashMap HashSet TreeMap TreeSet StringBulider 线程安全集合: Vector HashTable Properties 集合线程安全...
Java标准库中的一些类如ArrayList、HashMap和SimpleDateFormat,都是非线程安全的,在多线程环境下直接使用它们可能导致一些非预期的结果,甚至是一些灾难性的结果。一般来说,Java标准库中的类在其API文档中会说明...
Golang无锁线程安全的HashMap,为最快的读取访问进行了优化
HashMap的存储结构 HashMap内部采用数组和链表的方式存储数据,每个元素都包含...HashMap通过synchronized关键字实现线程安全,确保多线程环境下的数据一致性和并发访问的安全性,避免潜在的竞争条件和数据不一致问题。
HashMap 不是线程安全的;多线程环境下,建议使用 ConcurrentHashMap,或者使用 Collections.synchronizedMap(hashMap) 将 HashMap 转成线程同步的。 只能使用关联的键来获取值。 HashMap 只能存储对象,所以基本...
非线程安全:如果多个线程同时访问同一个HashMap实例,可能会导致数据不一致的问题。因此,在使用HashMap时需要进行同步处理或者使用线程安全的HashMap实现类。 动态扩容:当HashMap中的元素数量超过了容量(默认为...
面试中,HashMap可以说是必问的,既然这样,我们应该怎么准备怎么回答呢,看看这篇文章,估计你会懂点东西。 先看看这两张图,是其内部的存储结构 说起HashMap,我们可以先从底层实现说起,HashMap是通过hash算法...
如果我么需要有一个线程安全的HashMap,可以使用Collections.synchronizedMap(Map m)方法获得线程安全的HashMap,也可以使用ConcurrentHashMap类创建线程安全的map。 存储的元素在jdk1.7当中是Entry作为存储的
以结构图记录自己的记录自己面对的感触
哈希图总览Golang无锁无线程安全HashMap,针对最快的读取访问进行了优化。用法为地图中的键设置值: m := &HashMap{}m.Set("amount", 123)从地图中读取键的值: amount, ok := m.Get("amount")使用地图来计数URL请求...
什么是HashMap? HashMap是一个存储key-value...2.HashMap是线程不安全的,其速度比较快 3.HashMap在存储key的值时,允许为NULL 4.对于输入数据的顺序与输出数据的顺序没有特别要求(如果有特别要求,要用LinkedHashMap)
线程安全的集合 线程安全的 List CopyOnWriteArrayList 线程安全的Set 线程安全的Map ConcurrentHashMap ConcurrentSkipListMap java集合 线程不安全的集合 HashMap的特点 HashMap在Jdk8之前使用拉链法实现,jdk8...