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

最新文章

  1. apache虚拟主机访问原理
  2. Linux PPTP搭建
  3. ExtJS 等待两个/多个store加载完再执行操作的方法
  4. [C++]VS与第三方工具下载
  5. sublime 安装 Terminal 使用 cmder
  6. baba 运动网
  7. HAPROXY 配置项/配置实例
  8. redis简单配置
  9. java之路径
  10. python学习之数字
  11. 关于Excel数据批量导入数据库的案例
  12. Cypher查询语言--Neo4j 入门 (一)
  13. 文件类型解析漏洞防御与攻击(PHP)
  14. VB代码收集
  15. java入门-day02
  16. Linux命令——cat/less/more的区别
  17. 弗洛伊德算法Floyed(求各顶点间最短路径):可打印最短路径
  18. AJAX基本操作 + 登录 + 删除 + 模糊查询
  19. Codeforces 939E - Maximize!
  20. nginx反向代理缓存服务器的构建

热门文章

  1. eclipse上部署到tomcat不能自动部署maven管理的额jar包
  2. SpringCloud学习之手把手教你用IDEA搭建入门项目【番外篇】(一)
  3. python里的文件I/O
  4. JavaScript之HTML DOM Event
  5. centos6-7 yum安装php的方法
  6. 1-3.监督学习(supervised learning)
  7. 加速软件源更新和安装 ubuntu 软件中心
  8. android设备内部添加apn信息
  9. Druid数据库连接池获取连接阻塞(转载)
  10. linux中awk的应用