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

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

1public 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的实现:

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

1void 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[]:

1void 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(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

转载于:https://www.cnblogs.com/yangkai-cn/p/4016573.html

非线程安全的HashMap 和 线程安全的ConcurrentHashMap相关推荐

  1. JDK1.7和JDK1.8中HashMap是线程不安全的,并发容器ConcurrentHashMap模型

    一.HashMap是线程不安全的 前言 只要是对于集合有一定了解的一定都知道HashMap是线程不安全的,我们应该使用ConcurrentHashMap.但是为什么HashMap是线程不安全的呢,之前 ...

  2. HashMap为什么线程不安全

    一.学习目标 1.HashMap线程不安全原因: 原因: JDK1.7 中,由于多线程对HashMap进行扩容,调用了HashMap#transfer(),具体原因:某个线程执行过程中,被挂起,其他线 ...

  3. java 如何让HashMap变成线程安全的

    我们都知道.HashMap是非线程安全的(非同步的).那么怎么才能让HashMap变成线程安全的呢? 我认为主要可以通过以下三种方法来实现: 1.替换成Hashtable,Hashtable通过对整个 ...

  4. Java - 线程安全的 HashMap 实现方法及原理

    转载自 http://liqianglv2005.iteye.com/blog/2025016 Java HashMap 是非线程安全的.在多线程条件下,容易导致死循环,具体表现为CPU使用率100% ...

  5. hashmap扩容线程安全问题_HashMap线程不安全的体现

    1.多线程put操作后,get操作导致死循环. 2.多线程put非NULL元素后,get操作得到NULL值. 3.多线程put操作,导致元素丢失. 比如一个 ArrayList 类,在添加一个元素的时 ...

  6. hashmap扩容线程安全问题_HashMap在1.7 1.8中的线程安全问题

    HashMap的线程不安全主要体现在下面两个方面: 在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况. 在JDK1.8中,在并发执行put操作时会发生数据覆盖的情况. ? ? 常被问 ...

  7. hashmap扩容线程安全问题_HashMap是非线程安全,为什么ConcurrentHashMap能做到线程安全?...

    前言 我们都知道,HashMap是非线程安全的容器,那么为什么ConcurrentHashMap能够做到线程安全呢? 底层结构 首先看一下ConcurrentHashMap的底层数据结构,在Java8 ...

  8. php+linux+线程安全,转:PHP的线程安全ZTS与非线程(NTS)安全版本的区别

    原文来自于:http://blog.sina.com.cn/s/blog_94c21e8f0101s2ic.html Windows版的PHP从版本5.2.1开始有Thread Safe(线程安全)和 ...

  9. 美团点评高级1234面:算法+HashMap+Zookeeper+线程+Redis+kafka

    美团点评高级Java岗1-4面 坐标上海,金九银十拿下了美团点评高级Java岗的offer,薪资税前35k. PS. 1. 本篇依旧是咱们原来的文章风格,省略各种前情提要和勾搭过程,直击面试拷问细节. ...

最新文章

  1. gta5显示nat较为严格_报告显示,中国超七成企业正在使用灵活用工
  2. mysql select 报错
  3. 基于XML及注解配置方式实现AOP及aspectJ表达式
  4. Java中的访问限制符
  5. zoj3806Incircle and Circumcircle
  6. 20175213 2018-2019-2 《Java程序设计》第4周学习总结
  7. hibernate 调试_Hibernate调试–查找查询的来源
  8. 高压断路器故障诊断的相关方法
  9. 写出python字符串三种常用的函数或方法_python中几种常用字符串函数
  10. C# NameValueCollection
  11. R语言从原点开始作图
  12. HTTPS反向代理嗅探
  13. 今后几个月的IT读书计划
  14. 一个轻client,多语言支持,去中心化,自己主动负载,可扩展的实时数据写服务的实现方案讨论...
  15. javascript中的取input对象集合与php中取input数组的区别
  16. DiskTool 无损分区软件 免费易用的中文版“无损分区魔术师”(完美支持Win7/32与64位系统)...
  17. 火狐浏览器linux最新版本下载,火狐浏览器Linux最新版下载
  18. 【AAAI-2019】论文整理(清单)
  19. 2021-08-18
  20. D5 登录抽屉新热榜

热门文章

  1. 负margin的移位参考线
  2. c# 大数据量比较时-方案
  3. python 栈和队列 排序 初级数据结构
  4. log4j使用方法(一)——先跑起来再说
  5. ORACLE的基础用法
  6. docker 感性介绍
  7. oracle中的一些基本概念
  8. TeeChart经验总结 10.ZoomScroll
  9. 关于Window的快捷方式,图标缓存的清理
  10. 西门子获首都机场18亿大单