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

jdk1.6源码学习---ConCurrentHashMap

阅读更多

ConcurrentHashMap源码阅读心得:
    在看这个源码之前应该先学习hashMap和hashTable的区别,首先我们应该了解到该类有什么作用:该类相对于hashMap而言具有同步map中的数据,对于hashtable而言,该同步数据对于并发程序提高了极高的效率,所以在使用缓存机制的时候如果对map中的值具有高并发的情况的话,那么我们就需要使用concurrentHashMap。
    首先了解下concurrentHashMap的原理:该类是通过存放一个Segments(段数组),然后在每个Segments[i]中放入一个类似hashMap的结构,具体看下图:

 这个结构的好处是我们在并发访问的时候只需要对同一个段里面的数据锁定就可以保持相对的同步性,但是在ConcurrentHashMap中却只对put,remvoe进行了同步锁,而对get方法却没有使用同步。使用了关键字volatile(下面会简单介绍下这个)这样提高了效率。
    首先我们看一下在ConcurrentHashMap.java中包含的三个类:
    a.HashEntry:该类和HashMap中的Entry类似,唯一的却别在于构造函数,具体功能参考前章的hashmap和hashtable

        final K key;
        final int hash;
        volatile V value;//该volatile的作用是保持主内存和线程中的值同步,每个线程获取这个值的时候都会拷贝这份,这样就可以解决读的同步问题
        final HashEntry<K,V> next;

        HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
            this.key = key;
            this.hash = hash;
            this.next = next;
            this.value = value;
        }

 

    b.segment:该类其实你可以看成是一个hashMap,它和hashMap的结构非常相似,里面也是一个数组。唯一的却别在于volatile int count,就是在统计每个segment(段)的存储数量的时候进行了同步。构造函数这些和hashMap基本一致(在这里不重复讲解)   
    c.ConcurrentHashMap:该类有4个构造方法分别,也是中间相互调用,看下最主要的构造函数:

public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();

        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;
   
 //第一个参数是段里面的数组长度,第二个参数是效率因子(前面有讲到),第三个参数是段的长度 上面可以不用看。这里和hashMap的原理一样获取2的倍数来进行数组长度
        int sshift = 0;
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;//向左移位
      }
        segmentShift = 32 - sshift;
        segmentMask = ssize - 1;
        this.segments = Segment.newArray(ssize);//在这里创建了一个长度为ssize的数组,可见如果使用默认new concurrentHashMap的话段的长度将会永远固定,默认16个

                                                                           //,也就是说最多只有16个线程进行并发,所以这个参数对并发是非常关键的

        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = 1;//该参数是对段里面的数组进行长度设置
        while (cap < c)
            cap <<= 1;

        for (int i = 0; i < this.segments.length; ++i)
            this.segments[i] = new Segment<K,V>(cap, loadFactor);//创建每个段
    }
 

    和前面一样我们还是看最主要的方法:
    put方法:该方法是在setment(段)里面放入键值(根据hashcode确定segment段)

        public V put(K key, V value) {
            if (value == null)
                throw new NullPointerException();
                int hash = hash(key.hashCode());
            return segmentFor(hash).put(key, hash, value, false);
        }

 

    调用下面segment段中的put方法

      V put(K key, int hash, V value, boolean onlyIfAbsent) {//主要的区别在于对修改段的时候进行的同步的处理
            lock();//因为segment是继承ReentrantLock类,该类可以实现同步锁功能和synxxx什么的有一点点区别(这里我不多讲,)
            try {
                int c = count;
                if (c++ > threshold) // 这个是却断段里面是否需要对数组进行扩容
                    rehash();
                HashEntry<K,V>[] tab = table;
                int index = hash & (tab.length - 1);
                HashEntry<K,V> first = tab[index];//缺点段里面数组的位置
                HashEntry<K,V> e = first;
                while (e != null && (e.hash != hash || !key.equals(e.key)))
                    e = e.next;//获取key-value的HashEntry

                V oldValue;
                if (e != null) {
                    oldValue = e.value;
                    if (!onlyIfAbsent)
                        e.value = value;//值替换
                }
                else {
                    oldValue = null;
                    ++modCount;
                    tab[index] = new HashEntry<K,V>(key, hash, first, value);//值添加和hashMap一样
                    count = c;
                }
                return oldValue;
            } finally {
                unlock();//解锁
            }
        }

 

    remove方法和put一样
    看一下get方法:

        V get(Object key, int hash) {
            if (count != 0) { // read-volatile
                HashEntry<K,V> e = getFirst(hash);
                while (e != null) {
                    if (e.hash == hash && key.equals(e.key)) {
                        V v = e.value;
                        if (v != null)
                            return v;
                        return readValueUnderLock(e); //这个方法是当获得的value值为null的时候需要对其进行锁,这个好像是编译的时候初始化map会有问题。
                    }
                    e = e.next;
                }
            }
            return null;
        }

     该方法和hashmap基本没什么区别,所以对于concurrenthashmap而言它的读是异步了但是数据却保持了一致性这个就是volatile的好处,没有同步这样大大提高了效率

    下面还要介绍一下size方法,这个方法我觉得使用的很巧妙

    public int size() {
        final Segment<K,V>[] segments = this.segments;
        long sum = 0;//计算concurrenthashmap中的个数
        long check = 0;
        int[] mc = new int[segments.length];//统计所有段的修改次数
        for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {//进行2次非锁统计,如果还不行就把每个concurrenthashmap给锁了进行统计
            check = 0;
            sum = 0;
            int mcsum = 0;
            for (int i = 0; i < segments.length; ++i) {
                sum += segments[i].count;
                mcsum += mc[i] = segments[i].modCount;//判定在读取过程中有没有其他线程对它进行修改段内容操作
            }
            if (mcsum != 0) {
                for (int i = 0; i < segments.length; ++i) {
                    check += segments[i].count;
                    if (mc[i] != segments[i].modCount) {//这里很巧妙,如果修改段操作刚好是一增一减,那还是保持不变所以不用重新统计。
                        check = -1; // force retry
                        break;
                    }
                }
            }
            if (check == sum)//如果验证结果和统计结果一致就输出总和
                break;
        }
        if (check != sum) { // 如果2次统计还是不一致,则将每个段都锁了,再统计再进行解锁
            sum = 0;
            for (int i = 0; i < segments.length; ++i)
                segments[i].lock();
            for (int i = 0; i < segments.length; ++i)
                sum += segments[i].count;
            for (int i = 0; i < segments.length; ++i)
                segments[i].unlock();
        }
        if (sum > Integer.MAX_VALUE)
            return Integer.MAX_VALUE;
        else
            return (int)sum;
    }
 


以上草草讲了那么多,本人在研究这个类的时候花了一天的时间,学到了很多新名词和使用方法,比如volatile,ReentrantLock的lock方法,等等,可能中间还有不足之处,希望

多加指点,通过对这个类的学习,你可以对系统内存分配的性能进行调整,当你讲段分的越多的时候你系统的并发性将会大大的提高,这个需要根据实际情况而进行分配。多谢指点

  • 大小: 44 KB
分享到:
评论

相关推荐

    java-jdk1.6-jdk-6u45-windows-x64.zip

    1. 解压缩"java-jdk1.6-jdk-6u45-windows-x64.zip"文件,这将释放出"jdk-6u45-windows-x64.exe"可执行文件。 2. 双击运行"jdk-6u45-windows-x64.exe",安装向导会引导你完成安装过程。通常,你需要选择安装路径,...

    jdk-1.6-windows-64-01

    2部分: jdk-1.6-windows-64-01 jdk-1.6-windows-64-02

    JDK-1.6-Windows-32位 官方

    压缩包中的文件`jdk-6u45-windows-i586.exe`是JDK 1.6更新45的Windows 32位安装程序。安装步骤通常包括: 1. 下载并运行安装程序。 2. 遵循安装向导的提示,选择安装路径和组件。 3. 设置环境变量,包括`JAVA_HOME`...

    jdk1.6 解压版-windows

    JDK1.6是Oracle公司发布的一个较早版本,适用于Windows操作系统。在这个解压版中,用户无需进行安装过程,可以直接在Windows环境下使用JDK的各个工具。 JDK1.6包含的主要组件有: 1. **Java编译器**(javac):...

    jdk-1.6-linux-64-3

    三部分: jdk-1.6-linux-64-1 jdk-1.6-linux-64-2 jdk-1.6-linux-64-3

    aspose-words-15.8.0-jdk1.6

    aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-...

    jdk1.6-windows-x64

    在描述中,“jdk1.6 windows64版本 解压完直接双击安装即可”,意味着用户在下载该压缩包后,不需要进行复杂的设置或编译过程,只需将其解压缩,然后通过简单的鼠标操作,即双击名为“jdk-6u45-windows-x64.exe”的...

    jdk-jdk1.6.0.24-windows-i586.exe

    标题中的"jdk-jdk1.6.0.24-windows-i586.exe"是一个Java Development Kit(JDK)的安装程序,适用于Windows操作系统且为32位版本。JDK是Oracle公司提供的一个用于开发和运行Java应用程序的软件包。这个特定的版本,...

    jdk-1.6-linux-64-2

    三部分: jdk-1.6-linux-64-1 jdk-1.6-linux-64-2 jdk-1.6-linux-64-3

    jdk-1.6-windows-32-3

    三部分: jdk-1.6-windows-32-1 jdk-1.6-windows-32-2 jdk-1.6-windows-32-3

    jdk-1.6-linux-64-1

    三部分: jdk-1.6-linux-64-1 jdk-1.6-linux-64-2 jdk-1.6-linux-64-3

    logback-cfca-jdk1.6-3.1.0.0.jar

    logback-cfca-jdk1.6-3.1.0.0.jar

    zxing jar包,支持jdk1.6,包括源码

    - 这可能是ZXing库的完整源码包,专门针对JDK1.6编译,包含了所有必要的源文件和资源,供开发者进行更深度的定制和集成。 总之,ZXing库是一个强大的条形码和二维码工具,这个特别适配JDK1.6的版本为那些仍在使用...

    jdk1.6-windows-x64.rar

    jdk1.6安装版官方下载,JDK详细介绍 JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。 SE(J2SE),standard edition,标准版,是我们通常用的一个版本,从JDK 5.0开始,改名为Java SE。 EE(J2EE),...

    JDK1.6.0.24-64位

    JDK 1.6.0.24 是Java Development Kit的一个特定版本,专为64位操作系统设计。这个版本的发布旨在提供给那些需要特定JDK版本以兼容其应用程序或系统的用户。在Java开发中,JDK是至关重要的工具集,包含了编译、调试...

    Sun JDK 1.6内存管理--调优篇

    《Sun JDK 1.6内存管理--调优篇》深入探讨了Java开发中的关键环节——JVM内存管理和性能优化。Sun JDK 1.6作为早期的Java开发环境,其内存管理机制对于理解现代JVM的工作原理至关重要。本文将详细解析JVM内存结构,...

    jdk1.6_64-bit

    6. **并发改进**:JDK 1.6在并发包java.util.concurrent中添加了许多新的工具类,如ConcurrentHashMap,这为多线程环境下的编程提供了更好的性能和便利性。 7. **JMX(Java Management Extensions)**:允许开发者...

    jdk1.6 源代码--part2

    自己整理的完整版jdk1.6的source,包含了jdk包中原先的src.zip,自己重新压缩过。可以直接使用解压后的src.zip 完全解压后的代码有138M 方便调试跟踪 分了2部分,这是第二部分

    Jdk1.6-linux-x64

    运行这个文件时,通常需要赋予它执行权限(例如,使用`chmod +x jdk-6u34-linux-x64.bin`命令),然后通过终端执行它来启动安装过程。 另一个文件是"linux-jdk安装说明.txt",这提供了一个详细的指南,指导用户如何...

    jdk1.6 源码jdk1.6 源码

    深入理解JDK 1.6的源码对于Java开发者来说至关重要,因为它揭示了语言核心库的工作原理,有助于优化代码、理解和解决潜在问题。 1. **Java虚拟机(JVM)**:JDK 1.6中的JVM是Java程序执行的基础,它的主要职责是...

Global site tag (gtag.js) - Google Analytics