`
wsmajunfeng
  • 浏览: 497395 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

HashMap的非线程安全

 
阅读更多

在平时开发中,我们经常采用HashMap来作为本地缓存的一种实现方式,将一些如系统变量等数据量比较少的参数保存在HashMap中,并将其作 为单例类的一个属性。在系统运行中,使用到这些缓存数据,都可以直接从该单例中获取该属性集合。但是,最近发现,HashMap并不是线程安全的,如果你 的单例类没有做代码同步或对象锁的控制,就可能出现异常。

首先看下在多线程的访问下,非现场安全的HashMap的表现如何,在网上看了一些资料,自己也做了一下测试:

 1 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()  {
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的实现:

 1 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;
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,继续看:

1 void  addEntry( int  hash, K key, V value,  int  bucketIndex)  {
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[]:

 1 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];
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线程不安全的原因主要在于以下几个方面: 1. 多线程环境下并发修改:在多线程环境下,如果多个线程同时对HashMap进行读写操作,可能会导致数据的不一致性和数据丢失。例如,当一个线程正在执行put操作时,另...

    高级程序员必会的HashMap的线程安全问题,适用于0~2年的.7z

    然而,对于多线程环境,HashMap并不是线程安全的,这在并发编程中可能会引发一系列问题。本篇将深入探讨HashMap的线程安全问题,并提供相关的解决方案。 首先,我们需要了解HashMap在多线程环境下可能出现的问题: ...

    通过代码证明HashMap是线程不安全的(只用了一个Java文件)

    然而,`HashMap`在并发环境下并非线程安全。这个主题通常涉及到多线程编程和Java集合框架的基础知识。本篇文章将通过分析`HashMap`的源码以及编写一个简单的测试程序来证明这一点。 首先,我们要理解什么是线程安全...

    hashmap面试题_hashmap_

    答:HashMap非线程安全,而Hashtable是线程安全的;HashMap允许null键值,Hashtable不允;HashMap迭代器在修改时不会抛出ConcurrentModificationException,而Hashtable会。 5. HashMap的并发问题如何解决? 答:在...

    【并发】为什么HashMap是线程不安全的?

    经常会看到说HashMap是线程不安全的,ConcurrentHashMap是线程安全的等等说法,不禁有个疑问,什么是线程安全?什么样的类是线程安全的? 1.什么是线程安全性(what) 线程安全定义,最核心是正确性, 正确性:多个...

    hashmap-thread-test:测试 Java HashMap 是否是线程安全的

    1. **非线程安全**:`HashMap`不是线程安全的,因为它没有内置的同步机制来保护并发访问。当多个线程同时修改`HashMap`时,可能产生数据竞争和不确定的行为。 2. **性能**:`HashMap`通过使用散列函数快速查找键值...

    servlet线程安全问题

    2. 使用线程安全的对象:使用线程安全的对象,如 Vector、Hashtable 等,而不是 ArrayList、HashMap 等。 3. 使用锁机制:使用锁机制,如 synchronized 关键字,可以锁定某个对象,以避免多个线程同时访问同一个对象...

    java的hashMap多线程并发情况下扩容产生的死锁问题解决.docx

    此外,引入了ConcurrentHashMap类,这是一个专门为多线程设计的高效容器,其内部使用分段锁策略,可以在并发环境下保证线程安全,避免了类似HashMap扩容引发的死锁问题。 如果你在多线程环境中使用HashMap并遇到...

    HashMap源码分析系列-第四弹:HashMap多线程解决方案.docx

    #### 二、HashMap线程安全问题分析 在多线程环境中,`HashMap`的主要线程安全问题包括但不限于: 1. **链表死循环问题**:在JDK 1.7中,当多个线程同时进行`put`操作时,可能会出现链表死循环的情况,这是一个严重...

    一些java面试经验pdf

    - HashMap与Hashtable:HashMap非线程安全,而Hashtable是线程安全的,但性能较低,不推荐在现代Java中使用。 - HashMap与HashSet:HashMap存储键值对,HashSet存储元素,两者都基于哈希表,但HashSet是HashMap的...

    Java多线程编程深入详解.rar

    - **HashMap vs ConcurrentHashMap**:HashMap非线程安全,ConcurrentHashMap提供线程安全的并发访问。 - ** BlockingQueue**:线程安全的队列,适用于生产者-消费者模型。 7. **线程通信** - **wait(), notify...

    码出八股文-斩出offer线.pdf

    - 线程安全:HashMap 非线程安全,多线程环境下需手动同步;HashTable 是线程安全的,但效率较低。 - 扩容策略:HashTable 扩容时容量翻倍加一,HashMap 扩容时容量翻倍。 - hash 值计算:两者有不同的哈希值计算...

    Java 11道中级面试题.docx

    HashMap非线程安全但高效,允许键和值为null;Hashtable线程安全但效率低,不支持null键值。 9. **ArrayList与LinkedList**: - ArrayList基于数组,随机访问快,插入删除慢。 - LinkedList基于链表,插入删除快...

    map,list,set,stack,queue,vector等区别和特点1

    在Java编程语言中,集合框架是处理对象组织和操作的...在多线程环境中,线程安全的实现如Vector和Hashtable是必要的,但在单线程或性能要求较高的情况下,非线程安全的实现如ArrayList、LinkedList和HashMap通常更优。

    java实验04.pdf

    这有助于学习Map的键值映射概念以及HashMap和HashTable的区别,HashMap非线程安全但效率高,而HashTable则是线程安全的。 第三部分使用了Vector,这是一种线程安全的动态数组。实验要求读取用户输入的多个字符串,...

    最新java面试题及答案(基础篇)

    - **线程安全性**:HashMap非线程安全,Hashtable是线程安全的。 - **null值**:HashMap允许null键和值,Hashtable不允许。 - **效率**:HashMap通常比Hashtable更快,因为不保证线程安全。 - **迭代器**:...

    面试题(2).docx

    - HashMap非线程安全,允许空键值,效率高。 - HashTable线程安全,不允许空键值,已被淘汰,推荐使用ConcurrentHashMap。 9. **HashMap的底层实现和扩容机制** - JDK1.8之前,HashMap使用数组+链表,当链表长度...

Global site tag (gtag.js) - Google Analytics