`
xinklabi
  • 浏览: 1591326 次
  • 性别: Icon_minigender_1
  • 来自: 吉林
文章分类
社区版块
存档分类
最新评论

ConcurrentHashMap与HashMap的不同,以及二者的性能测试比较

 
阅读更多

好像今天没有什么源码读,那么就来看看java的这两种HashMap有啥不一样的地方吧,在这之前先普及一下HashMap的一些基本知识:

  (1)放入HashMap的元素是key-value对。

  (2)底层说白了就是以前数据结构课程讲过的散列结构。

  (3)要将元素放入到hashmap中,那么key的类型必须要实现实现hashcode方法,默认这个方法是根据对象的地址来计算的,具体我也记不太清楚了,接着还必须覆盖对象的equal方法。

  用一张图来表示一下散列结构吧:

  在这里hashCode函数就是用于确定当前key应该放在hash桶里面的位置,这里hash桶可以看成是一个数组,最简单的通过一些取余的方法就能用来确认key应该摆放的位置,而equal函数则是为了与后面的元素之间判断重复。

  好了,这里我们接下来来看看java的这两种类库的用法吧:

  由于他们都实现了Map接口,将元素放进去的方法就是put(a,b),这里我们先来分析比较简单的HashMap吧:

 

    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);  //获取当前key的hash值
        int i = indexFor(hash, table.length);  //返回在hash桶里面的位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {  //遍历当前hansh桶后面的元素
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  //如果有相同的key,那么需要替换value
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;  //返回以前的value
            }
        }

        modCount++;
        addEntry(hash, key, value, i);  //放入entry
        return null;
    }

 

  这个函数其实本身还是很简单的,首先通过hash函数获取当前key的hash值,不过这里需要注意的是,对hashCode方法返回的值HashMap本身还会进行一些处理,具体什么样子的就不细说了,然后再调用indexFor方法用于确定当前key应该属于当前Hash桶的位置,接着就是遍历当前桶后面的链表了,这里equal方法就派上用场了,这里看到如果equal是相等的话,那么就直接用新的value来替换原来的value就好了。。。

  当然最多的情况还是,桶后面的链表没有与当前的key相同的,那么这个时候就需要调用addEntry方法,将要加入的key-value放入到当前的结构中了,那么接下来来看看这个方法的定义吧:

 

    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);  //相当于重新设置hash桶
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);  //创建新的entry,并将它加入到当前的桶后面的链表中
    }

 

  其实这个方法很简单,首先来判断当前的桶的大小,如果觉得太小的话,那么需要扩充当前桶的大小,这样可以让添加元素存放的更离散化一些,优化擦入和寻找的效率。

  然后就是创建一个新的entry,用于保存要擦入的key和value,然后再将其链到应该放的桶的链表上就好了。。

  好了,到这里位置,整个HashMap的擦入元素的过程就已经看的很清楚了,在整个这个过程中没有看到有加锁的过程,因此可以说明HashMap是不支持并发的,不是线程安全的,在并发的环境下使用会产生一些不一致的问题。。。

  因此java新的concurrent类库中就有了ConcurrentHashMap用于在并发环境中使用。。

  那么我们再来看看ConcurrentHashMap的put操作是怎么搞的吧:

 

    public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);  //获取hash值
        int j = (hash >>> segmentShift) & segmentMask;
        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment  //用于获取相应的片段
            s = ensureSegment(j);   //这里表示没有这个片段,那么需要创建这个片段
        return s.put(key, hash, value, false);  //这里就有分段加锁的策略
    }

 

  这里刚开始跟HashMap都差不太多吧,无非是先获取当前key的hash值,但是接下来进行的工作就不太一样了,这里就有了一个分段的概念:

  ConcurrentHashMap将整个Hash桶进行了分段,也就是将这个大的数组分成了几个小的片段,而且每个小的片段上面都有锁存在,那么在擦入元素的时候就需要先找到应该插入到哪一个片段,然后再在这个片段上面进行擦入,而且这里还需要获取锁。。。。

  那我们来看看这个segment的put方法吧:

 

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
         //这里的锁是计数锁,同一个锁可以被同一个线程获取多次,但是不能被不同的线程获取
            HashEntry<K,V> node = tryLock() ? null :   //如果获取了当前的segment的锁,那么node为null,待会自己分配就好了
                scanAndLockForPut(key, hash, value);  //如果没有加上锁,那么等吧,有可能的话还要分配entry,反正有时间干嘛不多做一些事情
            V oldValue;
            try {
             //这里表示已经获取了锁,那么将在相应的位置放入entry
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> first = entryAt(tab, index);  //找到存放entry的桶,然后获取第一个entry
                for (HashEntry<K,V> e = first;;) {  //从当前的第一个元素开始
                    if (e != null) {
                        K k;
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {  //如果key相等,那么直接替换元素
                            oldValue = e.value;  
                            if (!onlyIfAbsent) {
                                e.value = value;   
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                         //如果元素太多了,那么需要重新调整当前的hash结构,让桶变多一些,这样元素放的更离散一些
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();  //这里必须要在finally里面释放已经获取的锁,这样才能保证锁一定会被释放
            }
            return oldValue;
        }





 其实在这里ConcurrentHashMap和HashMap的区别就已经很明显了:

  (1)ConcurrentHashMap对整个桶数组进行了分段,而HashMap则没有

  (2)ConcurrentHashMap在每一个分段上都用锁进行保护,从而让锁的粒度更精细一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。。。

  最后用一张图来表来说明一下ConcurrentHashMap吧:

  最后,在并发的情况下,要么使用concurrent类库中提供的容器,要么就需要自己来管理数据的同步问题了。。。

 

HashMapConcurrentHashMap的测试报告

日期:2008-9-10

 

测试平台:

CPUIntel Pentium(R) 4 CPU 3.06G     

内存:4G                             

操作系统:window server 2003 

 

一、HashMapConcurrentHashMap简单put操作的时间对比

 

1HashMap测试

A、程序代码:

package test0908;

import java.util.Map;

import java.util.HashMap;

 

public class HashmapTest {

    public static void main(String []args){  

       Map<Integer,Integer> hashmap = new HashMap<Integer,Integer>();

       int tt=13;

 

而循环100

Hashmap.put(i,”aaa”)

用时time = 2563ms

矩形标注: 而循环100万
Hashmap.put(i,”aaa”),
用时time = 2563ms       long begin1 = System.currentTimeMillis();

 

       for(int i=0; i<1000000; i++){

           tt = Math.abs(tt*(tt-i)-119);

           hashmap.put(tt, tt);

           //System.out.println(hashmap.get(tt));

       }  

       System.out.println("time="+(System.currentTimeMillis() - begin1)+"ms.");        

    }

}

 

B、测试结果截图(循环100万次):


put操作循环10万次时,得到time = 344ms,

循环50万次时,得到time = 1657ms,

循环100万次时,得到time =4094ms

 

 

2ConcurrentHashMap测试

A、程序代码:

package test0908;

import java.util.concurrent.ConcurrentHashMap;

 

public class conHashmapTest{

    public static void main(String []args){  

       ConcurrentHashMap<Integer,Integer> chashmap = newConcurrentHashMap<Integer,Integer>();

       int tt=13;

       long begin1 = System.currentTimeMillis();

       for(int i=0; i<1000000; i++){

           tt = Math.abs(tt*(tt-i)-119);

           chashmap.put(tt, tt);

           //System.out.println(hashmap.get(tt));

       }  

       System.out.println("time="+(System.currentTimeMillis() - begin1)+"ms.");        

    }

}

 

B、测试结果截图(循环100万次):


put操作循环10万次时,得到time =281ms,

循环50万次时,得到time = 1376ms,

循环100万次时,得到time =3625ms,

 

 

二、HashMapConcurrentHashMap  put操作的最多个数对比(即内存溢出)

 

1、 HashMap测试

测试结果截图:

运行程序,内存初值为:846M,内存峰值为:931Mput计数=1,030,604


 

2、 ConcurrentHashMap  测试

测试结果截图:

运行程序,内存初值为:847M,内存峰值为:931Mput计数=1,030,238

三、HashMapConcurrentHashMap  多线程操作的测试

 

1、  HashMap测试结果截图:(10put线程,8get线程)

 

平均每秒的get次数/get次数

 

矩形标注: 平均每秒的get次数/总get次数

平均每秒的put次数/Put次数

矩形标注: 平均每秒的put次数/总Put次数

 

 

2、  ConcurrentHashMap  测试结果截图 :(10put线程,8get线程)


 

3、  以上均设置睡眠1ms时, 平均每个线程达到510多;

每秒平均put的次数随线程的个数增加而增加,

 

4、注:当put线程数量为100get线程数量为90时,HashMap就开始出现性能下降的情形,CPU使用率达到45%左右,且putget的个数要明显少于ConcurrentHashMap的个数;

而使用ConcurrentHashMap时,则线程很稳定,CPU使用率不超过12%时。

测试结果截图:

 

concurrenthashmap相比,Putget线程达到100个条件下,hashmap要少5500左右

AHashMap测试图:

 


 

B、 ConcurrentHashMap测试图:


 

5、经反复测试发现,只要创建的putget的线程总数达到180个以上时,HashMap的性能就开始下降。而当创建的putget的线程总数达到256个以上时,ConcurrentHsahMap的性能也开始下降。

性能下降:CPU的使用率开始增加,平均每秒putget的个数开始下降,即出现若线程再增加,而putget反而减少。

 

 

发一篇原贴文章真不容易啊,尤其是还这么多图片的。嘻嘻!搞了我半个多钟头。这个测试报告是写给项目经理看的,但是很多同志说看不懂这个报告里面有什么../? 所以发上来希望各位高手狠狠的给点意见,小弟在此谢了!

最后贴上第三个测试中concurrenthashmap的源程序:

 

package test0908;

import java.util.concurrent.ConcurrentHashMap;

 

public class CHashmapTest {

public static void main(String []args){

try{

count c = new count();

ConcurrentHashMap<Integer,Integer> chm = new ConcurrentHashMap<Integer,Integer>();

for(int i=0; i<50; i++){

new putCHashmapThread(chm,c).start();//put操作

}

 

for(int i =0 ; i<45; i++){

new getCHashmapThread(chm,c).start();    //get操作

}

 

ProbeThread pt = new ProbeThread(c);    //监听线程

pt.start();

}catch(Exception e){

System.out.println(e.getMessage());

}

}

}

 

class putCHashmapThread extends Thread{     //put操作线程

private ConcurrentHashMap<Integer,Integer> chm = null;

private count c = null;

 

public putCHashmapThread(ConcurrentHashMap<Integer,Integer> chm,count c){

this.chm = chm;

this.c = c;

}

 

public void run(){

int tt = 13;

int i = 1;

try{

while(true){

tt = Math.abs(tt*(tt-i)-119);

chm.put(tt, tt);

c.addcount1();    //put操作计数

i++;

Thread.sleep(1);

//System.out.println(i);

}

}catch(Exception e){

System.out.println(e.getMessage());

}

}

}

 

class getCHashmapThread extends Thread{      //get操作线程

private ConcurrentHashMap<Integer,Integer> chm = null;

private count c = null;

 

public getCHashmapThread(ConcurrentHashMap<Integer,Integer> chm,count c){

this.chm = chm;

this.c = c;

}

 

public void run(){

int tt = 13;

int i = 1;

try{

while(true){

tt = Math.abs(tt*(tt-i)-119);

chm.get(tt);

c.addcount2();  //get操作计数

i++;

Thread.sleep(1);

//System.out.println(i);

}

}catch(Exception e){

System.out.println(e.getMessage());

}

}

}

 

class count{   //计数器

private static int count1 = 1, count2 = 1;

 

public int getcount1() {

return count1;

}

 

public int getcount2(){

return count2;

}

 

public void addcount1(){

count1++;

}

 

public void addcount2(){

count2++;

}

 

}

 

class ProbeThread extends Thread {     //监听线程

 

private boolean run = true;

count cc;

 

public ProbeThread(count cc) {

this.cc = cc;

}

 

@SuppressWarnings("static-access")

public void run() {

int c1=0, c2=0;

int cc1 = 0, cc2 = 0;

while(this.run) {

c2 = cc.getcount1();

cc2 =cc.getcount2();

System.out.println("put:"+"["+(c2-c1)/2+"/"+c2+"]"+"  get:"+"["+(cc2-cc1)/2+"/"+cc2+"]");

c1 = c2;

cc1 = cc2;

 

try {

Thread.sleep(1000*2-1);

} catch(Exception ex) {

System.out.println("Error[ProbeThread.run]:"+ex.getMessage());

}

}

}

}

分享到:
评论

相关推荐

    Java里多个Map的性能比较(TreeMap、HashMap、ConcurrentSkipListMap)

    在Java编程中,Map接口是用于存储键值对的数据结构,而Java提供了多种Map的实现,包括TreeMap、HashMap和ConcurrentSkipListMap。...在实际使用中,应结合性能测试和业务需求来确定最适合的Map类型。

    HashMap与ConcurrentHashMap面试要点.pdf

    ### HashMap和ConcurrentHashMap...综上所述,面试时通常会针对HashMap和ConcurrentHashMap的实现原理、它们的差异、以及在并发环境下如何保证数据的一致性和线程安全进行提问。了解这些知识点有助于在面试中脱颖而出。

    ConcurrentHashmap源码

    源码分析见我博文:http://blog.csdn.net/wabiaozia/article/details/50684556

    HashMap与CorruntHashMap性能对比

    然而,这并不意味着在所有多线程场景下`ConcurrentHashMap`都优于`HashMap`,如果线程访问的键值对分布在不同段,`HashMap`的性能可能会更好,因为`ConcurrentHashMap`的分段锁在某些情况下可能导致过多的锁粒度。...

    Java并发编程笔记之ConcurrentHashMap原理探究.docx

    Java并发编程中的ConcurrentHashMap是HashMap的一个线程安全版本,设计目标是在高并发场景下提供高效的数据访问。相比HashTable,ConcurrentHashMap通过采用锁分离技术和更细粒度的锁定策略来提升性能。HashTable...

    HashMap&ConcurrentHashMap.key

    HashMap& ConcurrentHashMap 深度解析

    java7-8中的 HashMap和ConcurrentHashMap全解析

    总的来说,`HashMap`和`ConcurrentHashMap`都是Java中处理键值对的重要工具,它们各自具有不同的特性和使用场景。理解它们的工作原理和优缺点,能够帮助开发者更有效地使用这些数据结构,优化代码性能。对于Java...

    java7-8中的 HashMap和ConcurrentHashMap全解析.pdf

    在Java 7和8中,HashMap和ConcurrentHashMap是两种重要的数据结构,分别用于非线程安全和线程安全的键值对存储。本篇文章将深入解析这两种数据结构的内部实现,帮助读者理解它们的工作原理。 HashMap是Java中最常用...

    HashMap,HashTable,ConcurrentHashMap之关联.docx

    ConcurrentHashMap 是 Java 中的另一个线程安全的类,它与 HashTable 在线程同步上有什么不同?ConcurrentHashMap 使用了分段锁的机制,每个段都独立锁定,提高了并发性能。 HashMap 和 HashTable 的区别 1. 线程...

    详谈HashMap和ConcurrentHashMap的区别(HashMap的底层源码)

    与HashMap不同的是,ConcurrentHashMap是线程安全的,它使用了锁分段的机制,每个段都独立加锁,避免了多线程之间的竞争,提高了并发效率。ConcurrentHashMap的实现是基于HashMap的,它将数据分为多个segment,每个...

    2.Java7_8+中的+HashMap+和+ConcurrentHashMap+全解析1

    与HashMap不同,ConcurrentHashMap采用了分段锁(Segment)的设计,将整个散列表分为多个独立的段,每个段有自己的锁。这样,在多线程环境下,不同的线程可以同时对不同段进行操作,提高了并发性能。每个段内部仍然...

    hashmap面试题_hashmap_

    HashMap作为Java集合框架中的重要成员,是面试中常见的知识点,尤其在数据结构与算法、并发编程以及JVM内存管理等领域,HashMap的深入理解至关重要。本篇将围绕HashMap的相关面试题,从基础概念到高级应用进行详尽...

    HashMap与HashTable区别

    ### HashMap与HashTable的区别 在Java编程语言中,`HashMap`和`HashTable`是两种非常重要的数据结构,它们都实现了`Map`接口,并提供了键值对的存储方式。这两种数据结构虽然相似,但在实现细节和使用场景上存在...

    Java利用ConcurrentHashMap实现本地缓存demo

    Java利用ConcurrentHashMap实现本地缓存demo; 基本功能有缓存有效期、缓存最大数、缓存存入记录、清理线程、过期算法删除缓存、LRU算法删除、获取缓存值等功能。 复制到本地项目的时候,记得改包路径哦~

    HashMap的数据结构

    9. ** equals() 和 hashCode()**:插入HashMap的键对象必须正确地重写`equals()`和`hashCode()`方法,以确保键的比较逻辑与哈希码的计算一致,否则可能会导致预期之外的行为。 10. **resize()**:当HashMap达到其...

    hashmap 实例

    《HashMap 实例解析与关联数据结构对比》 HashMap 是 Java 中常用的一种数据结构,属于 Java.util 包下的类,它是基于哈希表实现的。在本文中,我们将深入理解 HashMap 的实例及其工作原理,并与其他数据结构如 ...

    HashMap底层实现原理HashMap与HashTable区别HashMap与HashSet区别.docx

    HashMap适合单线程环境和对性能要求高的场合,HashTable适用于需要线程安全但对性能要求不那么高的情况,而HashSet则是存储不重复元素的高效选择。了解它们的底层实现和区别,有助于在实际编程中做出最优的选择。

    一文让你彻底理解JavaHashMap和ConcurrentHashMap

    本篇主要想讨论ConcurrentHashMap这样一个并发容器,在正式开始之前我觉得有必要谈谈HashMap,没有它就不会有后面的ConcurrentHashMap。众所周知HashMap底层是基于数组+链表组成的,不过在jdk1.7和1.8中具体实现稍有...

Global site tag (gtag.js) - Google Analytics