`

java需要关注的知识点---ConcurrentHashMap

阅读更多
ConcurrentHashMap默认初始大小 16,临界值:12:基数:0.75
1.ConcurrentHashMap是一个线程安全的hashMap。相对hashMap多出以下一些特殊属性:
    //默认能够同时运行的线程数目
    static final int DEFAULT_CONCURRENCY_LEVEL = 16;
    //最大同时运行的线程数目
    static final int MAX_SEGMENTS = 1 << 16; // slightly conservative


2.ConcurrentHashMap的链表实例HashEntry:
 static final class HashEntry<K,V> {
        final K key;
        final int hash;
        volatile V value;
        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;
        }

	@SuppressWarnings("unchecked")
	static final <K,V> HashEntry<K,V>[] newArray(int i) {
	    return new HashEntry[i];
	}
    }

这里需要注意的是Value,value并不是final的,而是一个volatile.
volatile修饰符告诉编译程序不要对该变量所参与的操作进行某些优化。在两种特殊的情况下需要使用volatile修饰符:
第一种情况涉及到内存映射硬件(memory-mapped hardware,如图形适配器,这类设备对计算机来说就好象是内存的一部分一样),
第二种情况涉及到共享内存(shared memory,即被两个以上同时运行的程序所使用的内存)。
    大多数计算机拥有一系列寄存器,其存取速度比计算机主存更快。好的编译程序能进行一种被称为“冗余装入和存储的删去”(redundant load and store removal)的优化,即编译程序会在程序中寻找并删去这样两类代码:一类是可以删去的从内存装入数据的指令,因为相应的数据已经被存放在寄存器中;另一种是可以删去的将数据存入内存的指令,因为相应的数据在再次被改变之前可以一直保留在寄存器中。
    如果一个指针变量指向普通内存以外的位置,如指向一个外围设备的内存映射端口,那么冗余装入和存储的优化对它来说可能是有害的。

ConcurrentHashMap不同于HashMap中的一点是,concurrentHashMap的put,get,remvoer等方法的实现都是由其内部类Segment实现的,该内部类:

static final class Segment<K,V> extends ReentrantLock implements Serializable {.....}

可以看出,该类实现了重入锁保证线程安全,使用final修饰保证方法不被篡改。


3.ConcurrentHashMap 中的 readValueUnderLock
 V readValueUnderLock(HashEntry<K,V> e) {
            lock();
            try {
                return e.value;
            } finally {
                unlock();
            }
        }

该代码是在值为空的情况才调用;该方法在锁定的情况下获取值。由该方法的注释可以得知,这样做是为了防止在编译器重新定制一个指定的HashEntry实例初始化时,在内存模型中发生意外。
  /**
         * Reads value field of an entry under lock. Called if value
         * field ever appears to be null. This is possible only if a
         * compiler happens to reorder a HashEntry initialization with
         * its table assignment, which is legal under memory model
         * but is not known to ever occur.
         */


3.ConcurrentHashMa中的put方法:
   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);
    }

不允许null的键
可以看出,ConcurrentHashMap和HashMap在对待null键的情况下截然不同,HashMap专门开辟了一块空间用于存储null键的情况,而ConcurrentHashMap则直接抛出空值针异常。
4.ConcurrentHashMa中segment的put方法:
V put(K key, int hash, V value, boolean onlyIfAbsent) {
            lock();
            try {
                int c = count;
                if (c++ > threshold) // ensure capacity
                    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;

                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);
                    count = c; // write-volatile
                }
                return oldValue;
            } finally {
                unlock();
            }

从该方法可以看出,根据key的hash值,计算到table下标位置之后,获取该下标位置的Entry链表,然后从链表第一个位置开始向后遍历,分别比对entry的hash值和key的值,如果都相等且entry不为空,则获取 该entry,设置该entry的value为传入的value,否则往后遍历直到链表中最后一个位置,直到找到相匹配的key和hash;如果e为空,则往该index下插入一个新的entry链表。
该方法使用了重入锁用以保证在同步时候线程的安全。

5.ConcurrentHashMa中segment的rehash方法(当前数组容量不够,进行扩充的操作):
 void rehash() {
            HashEntry<K,V>[] oldTable = table;
            int oldCapacity = oldTable.length;
            //如果数组的长度大于或等于临界值,数组不再进行扩容。
            if (oldCapacity >= MAXIMUM_CAPACITY)
                return;  
            //扩充数组容量为原来大小的两倍。
            HashEntry<K,V>[] newTable = HashEntry.newArray(oldCapacity<<1);
            //重新计算临界值
            threshold = (int)(newTable.length * loadFactor);
            
            int sizeMask = newTable.length - 1;
            for (int i = 0; i < oldCapacity ; i++) {
                // We need to guarantee that any existing reads of old Map can
                //  proceed. So we cannot yet null out each bin.
                HashEntry<K,V> e = oldTable[i];

                if (e != null) {
                    HashEntry<K,V> next = e.next;
                    //获取该链表在数组新的下标
                    int idx = e.hash & sizeMask;

                    //该链表不存在后续节点,直接把该链表存入新数组,无需其他操作
                    if (next == null)
                        newTable[idx] = e;

                    else {
                        // 存在后续节点,使用临时变量存储该链表,假设当前节点是最后节点。
                        HashEntry<K,V> lastRun = e;
                        //获取下标
                        int lastIdx = idx;
                        //遍历该链表的后续节点
                        for (HashEntry<K,V> last = next;
                             last != null;
                             last = last.next) {
                            //获取后一个节点的index
                            int k = last.hash & sizeMask;
                            //如果后一个节点的index值和前一个不相同,
                            //则使用后节点的index覆盖前一个节点并且设置该节点为最后节点,依次
                            //做相同的操作,直到链表的最后一个节点。
                            if (k != lastIdx) {
                                lastIdx = k;
                                lastRun = last;
                            }
                        }                       
                      //把链表最后节点的值传递给数组
                      //该数组下标为最后获取到的下标
                      newTable[lastIdx] = lastRun;

                        // 遍历老数组下得到的链表的节点值,复制到新的扩容后的数组中。
                        for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {                   
                            //计算链表在新数组的下标
                            int k = p.hash & sizeMask;
                            //获取数组k下标的链表值。
                            HashEntry<K,V> n = newTable[k];
                            //把获取到的链表作为需要插入的新的entry的后续节点。
                            newTable[k] = new HashEntry<K,V>(p.key, p.hash,
                                                             n, p.value);
                        }
                    }
                }
            }
            //把扩容后的数组返回
            table = newTable;
        }

该方法的描述见代码注释
6.ConcurrentHashMap中的remove方法:
  public V remove(Object key) {
	int hash = hash(key.hashCode());
        return segmentFor(hash).remove(key, hash, null);
    }

7.ConcurrentHashMa中segment的remove方法:
 V remove(Object key, int hash, Object value) {
            lock();
            try {
                int c = count - 1;
                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;

                V oldValue = null;
                if (e != null) {
                    V v = e.value;
                    if (value == null || value.equals(v)) {
                        oldValue = v;
                        // All entries following removed node can stay
                        // in list, but all preceding ones need to be
                        // cloned.
                        ++modCount;
                        HashEntry<K,V> newFirst = e.next;
                        for (HashEntry<K,V> p = first; p != e; p = p.next)
                            newFirst = new HashEntry<K,V>(p.key, p.hash,
                                                          newFirst, p.value);
                        tab[index] = newFirst;
                        count = c; // write-volatile
                    }
                }
                return oldValue;
            } finally {
                unlock();
            }
        }

类似于put方法,remove方法也使用了重入锁来保证线程安全;concurrentHashMap的remove方法不同于HashMap的remove方法,在需要删除元素的index下的entry链表没有后续节点时候;后者的remove方法自己会负责回收删除元素的内存并且会移动删除元素后面的元素来覆盖删除元素的位置,concurrentHashMap的remove方法只会回收内存却不会和HashMap一样移动元素。
分享到:
评论

相关推荐

    Java多线程-知识点梳理和总结-超详细-面试知识点.docx

    "Java多线程-知识点梳理和总结-超详细-面试知识点" Java多线程是Java编程语言中最基本也是最重要的概念之一。多线程编程可以提高程序的执行效率、改善用户体验和提高系统的可扩展性。但是,多线程编程也存在一些...

    「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识 准备 Java 面试,首选.zip

    Java 基础常见知识点&面试题总结(上) Java 基础常见知识点&面试题总结(中) Java 基础常见知识点&面试题总结(下) 重要知识点详解 : 为什么 Java 中只有值传递? Java 序列化详解 泛型&通配符详解 Java 反射机制详解...

    「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识

    Java 基础常见知识点&面试题总结(上) Java 基础常见知识点&面试题总结(中) Java 基础常见知识点&面试题总结(下) 重要知识点详解: 为什么 Java 中只有值传递? Java 序列化详解 泛型&通配符详解 Java 反射机制详解 ...

    java核心知识点整理.pdf

    25 JAVA8 与元数据.................................................................................................................................25 2.4. 垃圾回收与算法 .................................

    java高并发相关知识点.docx

    Java高并发相关知识点包括: 线程:Java多线程的实现方式,包括继承Thread类和实现Runnable接口。 锁:Java中的锁机制,包括synchronized关键字和ReentrantLock类。 线程池:Java中的线程池机制,包括线程池的创建...

    JAVA核心知识点整理(有效)

    25 JAVA8 与元数据.................................................................................................................................25 2.4. 垃圾回收与算法 .................................

    01java基础-集合知识点详解.xlsx

    就是一些通用java集合知识点整理,ArrayList LinkedList,HashMap,HashTable ,ConcurrentHashMap,HashSet,LinkedHashSet类通过线程安全否: 底层: 初始值: 扩容 : 区别(对比优势) 图解

    Java_Android笔试、面试知识整理.pdf

    Java 基础知识点包括面向对象基础、运算符优先级、集合框架、Java 分派机制、Java 异常、Java 泛型、Java 线程、JVM 架构、类加载器、JVM 类加载三步走、JVM 垃圾回收、Java 对象生命周期、Volatile 原理、...

    java7hashmap源码-Rebuild-Java:再度重修JAVA

    虽然JAVASE看完了,但仔细一想,好像有很多似懂非懂的知识点,好像会,但仔细一想,却不会。 因此重修JAVA 着重看集合,IO,多线程 泛型例子--&gt;来自java编程思想--P397 Holder.java CovariantArrays.java ...

    Java后端面试问题整理.docx

    Java后端面试知识点总结,涉及JVM • 熟悉JVM内存区域,常用引用类型,垃圾回收机制、算法以及常见的GC垃圾收集器(Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1) • 熟悉常用IO模型(BIO、...

    HashMap,HashTable,ConcurrentHashMap之关联.docx

    HashMap,HashTable,...Java 集合类是非常重要的知识点,HashMap、HashTable、ConcurrentHashMap 是集合类中的重点。理解它们的内部结构和特点是非常重要的,为此,我们需要不断学习和实践,掌握它们的原理和应用。

    java中级面试题(自己汇总)

    本文总结了Java中级面试题,涵盖了集合、HashMap、HashSet、HashTable、ConcurrentHashMap、红黑树、Java 8对HashMap的优化、LinkedHashMap、TreeMap、IdentityHashMap等知识点。 集合 * List和Set都是继承自...

    JAVA基础技术框架详解二.pdf

    本资源摘要信息中,我们将详细介绍 Java 相关技术框架的各种知识点,涵盖了 Java 语言基础、Java 虚拟机(JVM)、Java 集合框架、Java 并发编程、Java 网络编程、数据存储技术等方面的知识。 Java 语言基础 * Java...

    面试题全集(周瑜).pdf

    以下是Java基础知识点的总结,涵盖了Java语言的基本概念、Java虚拟机(JVM)、Java集合框架、Java多线程编程、Java异常处理、Java反射机制、Java IO流、Java网络编程、Java安全机制等方面。 一、Java语言基础 * ...

    2022 最全 Java 面试笔试题汇总

    Java 面试题汇总 本文档旨在对 Java 面试中常见的问题进行总结和解释,为读者提供一个系统的 Java 面试...本文档涵盖了 Java 面试中的大部分知识点,旨在帮助读者系统地学习和掌握 Java 相关知识,提高面试的通过率。

    java面试题,180多页,绝对良心制作,欢迎点评,涵盖各种知识点,排版优美,阅读舒心

    数据类型自动提升(注意以下讨论的是二元操作符) 16 【基础】switch支持的类型 17 【基础】当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是...

    Java面试总结,Redis宕机数据丢失解决方案,看完这篇彻底明白了.docx

    本文将从四个方面详细讲解Java面试的重要知识点: 一、Java基础知识 1. HashMap的内部结构、内部原理和HashTable的区别 * HashMap的内部结构主要是数组和链表的结合,通过hash函数将键映射到数组中的索引上,然后...

    JAVA经典面试题附答案

    JAVA基础知识点: 1. JAVA中的基本类型: JAVA中的基本类型有八种:byte、short、int、long、float、double、char、boolean。每种类型占用的字节数分别为:1、2、4、8、4、8、2、1。 2. String类的特点: String...

    java7hashmap源码-fantastic-blogs:记录一些看到过的相见恨晚的技术文章,收录原则:通俗易懂,详细

    java7 hashmap源码 记录一些看到过的相见恨晚的技术文章, ...JAVA其他知识点 并发 分布式 服务器 Tomcat Tomcat Connector配置及应用 Nginx Spring AOP MVC Spring Cloud HTTP & HTTPS 编码 数据库 索引&Tips

    Java程序员面试题与经验工与总结.docx

    本文总结了 Java 程序员面试中常见的知识点和经验总结,涵盖了 Java 基础、多线程、IO 与 NIO、虚拟机、设计模式、数据结构与算法、计算机网络、操作系统、主流框架、数据存储、分布式系统等方面的知识点。...

Global site tag (gtag.js) - Google Analytics