在平时开发中,我们经常采用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() {

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

 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;

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

 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];

        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的更多相关文章

  1. 如何使用线程安全的HashMap

    转载:https://blog.csdn.net/qq_31493821/article/details/78855069 HashMap为什么线程不安全 导致HashMap线程不安全的原因可能有两种 ...

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

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

  3. HashMap为什么线程不安全(hash碰撞与扩容导致)

    一直以来都知道HashMap是线程不安全的,但是到底为什么线程不安全,在多线程操作情况下什么时候线程不安全? 让我们先来了解一下HashMap的底层存储结构,HashMap底层是一个Entry数组,一 ...

  4. HashMap变成线程安全方法

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

  5. 为什么说 HashMap 是非线程安全的?

    我们在学习 HashMap 的时候,都知道 HashMap 是非线程安全的,同时我们知道 HashTable 是线程安全的,因为里面的方法使用了 synchronized 进行同步. 但是 HashM ...

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

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

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

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

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

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

  9. hashMap的线程不安全

    hashMap是非线程安全的,表现在两种情况下: 1 扩容: t1线程对map进行扩容,此时t2线程来读取数据,原本要读取位置为2的元素,扩容后此元素位置未必是2,则出现读取错误数据. 2 hash碰 ...

随机推荐

  1. 18 12 06 sql 的 基本语句 查询 条件查询 逻辑运算符 模糊查询 范围查询 排序 聚合函数 分组 分页 连接查询 自关联 子查询

    -- 数据的准备 -- 创建一个数据库 create database python_test charset=utf8; -- 使用一个数据库 use python_test; -- 显示使用的当前 ...

  2. Linux下mysql5.7安装

    当前最新版本为5.7,此次将分别采用yum安装和tar包编译安装的方式分别说明. 一.Yum安装 A:获取repo源 [root@localhost ~]# wget http://dev.mysql ...

  3. Python笔记_第五篇_Python数据分析基础教程_前言

    1. 前言: 本部分会讲解在Python环境下进行数值运算.以NumPy为核心,并讲解其他相关库的使用,诸如Matplotlib等绘图工具等. C.C++和Forttran等变成语言各有各的优势,但是 ...

  4. MBR&/BOOT&GRUB

      能正常工作的grub应该包 括一下文件:stage1.stage2.*stage1_5.menu.lst. 其中stage1要被安装(也就是写入)某个硬盘的主引导记录,或者某个活动分区(这个分区要 ...

  5. Codeforces 405D 数学问题

    真是脑残...擦 具体题解在这里 http://www.cnblogs.com/windysai/p/3619222.html 原本我为了防止两个数冲突,设置了好多判断,结果发现,如果两个数冲突,另外 ...

  6. 干货 | 京东云原生容器—SpringCloud实践(一)

    "云原生"成为近年热词并不是一种偶然,它不是一个软件,也不是一种框架,而是一堆理念集合,以及围绕这些理念所产生的一些最佳实践的工具.云原生天然就是作用于服务架构的,可以视作一个服务 ...

  7. 关于阿里云的远程连接和轻型桌面(xfce4)安装

    这里用的阿里云服务器是轻量应用服务器 先通过网页端的远程连接进入服务器,然后 安装xfce4 (1)先安装更新:apt-get update. (2)安装xrdp:输入apt-get install ...

  8. try{}catch{}finally{}使用总结

    import java.util.Scanner; class MyException extends Exception { public MyException(String Message) { ...

  9. 客户主题分析(tableau)—客户留存

    客户留存分析(客户漏斗分析),关键在于找到影响客户留存的因素,设计场景测试,验证关键因素.即可以通过关键因素影响留存,从而重塑客户漏斗到更有价值的形状. 案例:母婴产品客户留存分析 数据结构:  1) ...

  10. h5-语义化标签的兼容性问题

    1.html代码 <header>头</header> <nav>导航栏</nav> <main> <article>左< ...