hashmap和concurrentHashMap

转载自:https://blog.csdn.net/valada/article/details/79909905

本次 Chat 内容包括:

  • Hashmap 在 1.8 后有哪些变化,性能如何进行了大幅度提升?
  • 红黑树和链表在哪些情况会相互转化?
  • ConcurrentHashMap 在 1.8 后有哪些变化?
  • 什么是 CAS,锁同步消耗资源和无锁算法究竟哪个更合适?
  • 仿照 ConcurrentHashMap 源码,我们可以借鉴哪些并发编程思维?
  • 面试中常问到问题汇总--->独家经验

半年前写过《JAVA8 hashmap 源码阅读笔记》,总阅读量将5000。大家可以先看看。。

HashMap 简述

        HashMap 是由于数组+链表组成,数组默认初始容量为 16,Hash 表每次扩容都变为原来的两倍,默认负载因子的大小为0.75(若小于0,抛异常),负载因子是表示一个散列表的空间的使用程度,有这样一个公式:数组容量*负载因子大小=HashMap的容量,如果负载因子是默认的0.75,HashMap(16)的时候,占16个内存空间,实际上只用到了12个,超过12个就扩容(后面介绍,扩容操作及其耗费时间)。如果负载因子是1的话,HashMap(16)的时候,占16个内存空间,实际上会填满16个以后才会扩容。

JAVA8 中 HashMap 有哪些优化呢?

JAVA8 提出 Lambda 表达式,并提供全面支持集合类。

JAVA8 以前循环 Map 常用遍历方式:

    for (String string : map.keySet()) {        System.out.println(string + map.get(string));    }

JAVA8 新循环 Map 常用遍历方式:

    map.forEach((k,v) -> System.out.println(k + v));

以上是 Java8 的遍历写法,是不是很便捷呢?关于 JAVA8Lambda 表达式可以看JAVA 8 Lambda详解
        在 JAVA8 后底层优化链表,当发生 Hash 冲突时,冲突位置链表长度达8(可修改)时,将链表转化为红黑树,若发生冲突位置红黑树元素少于6(可修改)时,将红黑树转化为链表。也就是在发生冲突达到一定程度时。使用红黑树查找,删除的效果都比链表平均好很多。

HashMap 方法浅析

扫描二维码关注公众号,回复: 2805614 查看本文章

put 方法:算出 Hash 码值,查找指定地点是否发生 Hash 冲突,若发生冲突则继续判断,若 key 值相同,则用新的 Value 值替换之,若冲突位置已经是红黑树,按照红黑树方式,插入 key、Value。否则循环链表到尾端,若此时链表内元素大于等于7且表内元素数大于,将链表转化为红黑树,将值插入,将 size + 1,若超过最大值(数组容量*负载因子大小),则扩容,将容量扩容为2倍,将表内元素全部重新插入。
get方法:算出 Hash 码值,若 Map 不为空,Size 不为0,若不存在返回 null,若调用 equal 相同,直接返回,否则链表向下循环,红黑树,采用红黑树高效查找方式查找。
remove方法:按照 get 方法查找方式,若找到该节点,将该节点移除。
foreach方法:

public final void forEach(Consumer<? super K> action) {        Node<K,V>[] tab;        if (action == null)            throw new NullPointerException();        if (size > 0 && (tab = table) != null) {            int mc = modCount;            // 根据size循环            for (int i = 0; i < tab.length; ++i) {                for (Node<K,V> e = tab[i]; e != null; e = e.next)                    依次取值                    action.accept(e.key);            }            if (modCount != mc)                throw new ConcurrentModificationException();        }    }

再此 HashMap 的暂别,有问题的朋友可以先看 HashMap 详解,本文将主要讲解 ConcurrentHashMap;

ConcurrentHashMap 简述

        ConcurrentHashMap 是 jdk1.5 提出的新并发类,由于它可以高效地支持并发操作,目前被广泛使用,对于存在并发操作如 Spring、Servlet 等源码大量使用该类。与同是线程安全的 HashTable 相比,因此它的锁更加细化,而不是像 HashTable 一样为几乎每个方法都添加了 synchronized 锁(Collections.synchronized 相同(JDK1.6 优化 synchronized,其效率据说和 Lock 那些锁类差不多)),这样的锁会影响到并发操作性能。ConcurrentHashMap 在 JAVA 6、7、8 实现都有所不同每一次都进行进一步优化。甚至实现线程安全的思想也已经完全变了,在 JAVA8 中它摒弃了 Segment(锁段)的概念,而是启用了一种全新的方式实现,利用 CAS 算法。底层依然由“数组”+链表+红黑树的方式思想,但是为了做到并发,又增加了很多辅助的类,例如 TreeBin、Traverser等对象内部类。

ConcurrentHashMap 中基础知识

        常见到某个变量用 final 修饰,这种行为在 ConcurrentHashMap 中更常见,使用 final 关键字修饰变量,可以使其初始化尽可能的安全,使其不需要同步就能被自由的访问和共享。

        常见到某个变量用 volatile 修饰,在 Java5 之前,Java 内存模型对 volatile 的定义是:保证读写 volatile 都直接发生在 main memory(主线程) 中,线程的 working memory 不进行缓存。它只承诺了读和写过程的可见性,并没有对 Reording 做限制,所以旧的 volatile 并不太可靠。在 Java5 之后,JMM 对 volatile 的语义进行了增强。就是保证变量的修改能立刻被其他线程可见(底层通过内存栅栏实现)。volatile 可以保证对变量的修改可以被其他线程立刻看到,但不能保证该变量状态的改变对于其他线程来说是一种原子化操作。为了保证原子化操作,引入 CAS 无锁算法(后文详细讲解)。

如何让多线程之间,对象的状态对于各线程的“可视性”是顺序一致的:ConcurrentHashMap 使用了 happens-before 规则来实现。happens-before规则(摘取自 JAVA 并发编程):

  • 程序次序法则:线程中的每个动作A都 happens-before 于该线程中的每一个动作B,其中,在程序中,所有的动作B都能出现在A之后。
  • 监视器锁法则:对一个监视器锁的解锁 happens-before 于每一个后续对同一监视器锁的加锁。
  • volatile 变量法则:对 volatile 域的写入操作 happens-before 于每一个后续对同一个域的读写操作。
  • 线程启动法则:在一个线程里,对 Thread.start 的调用会 happens-before 于每个启动线程的动作。
  • 线程终结法则:线程中的任何动作都 happens-before 于其他线程检测到这个线程已经终结、或者从 Thread.join 调用中成功返回,或 Thread.isAlive 返回 false。
  • 中断法则:一个线程调用另一个线程的 interrupt happens-before 于被中断的线程发现中断。
  • 终结法则:一个对象的构造函数的结束 happens-before 于这个对象 finalizer 的开始。
  • 传递性:如果 A happens-before 于 B,且 B happens-before 于 C,则 A happens-before于C

这种理论性的书上面内容不好理解,我说一下我的理解:

        假设代码有两条语句,代码顺序是语句1先于语句2执行;那么只要语句之间不存在依赖关系,那么打乱它们的顺序对最终的结果没有影响的话,那么真正交给CPU去执行时,他们的执行顺序可以是先执行语句2然后语句1。

CAS 无锁算法浅析

        Compare and Swap是比较并交换的意思,是一种非阻塞算法 (nonblocking algorithms)。

        CAS 有3个操作数,内存值V,预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

        与锁相比,由于其采用无锁机制,所以他是非阻塞性,它对死锁问题天生免疫,线程间的相互影响也远远比基于锁的方式要小。无锁算法带来的系统开销远比加锁优秀的很多。

        如 i++,就是一个原子操作,在并发情况可能出错,可以使用 CAS 保证正确执行,关于算法执行大家也可以看我的另一文章并发编程之 CAS 无锁实现

ConcurrentHashMap 中重要的属性(与 HashMap 相同不在重复):

        sizeCtl 是一个控制标识符,在不同的地方有不同用途,而且它的取值不同,也代表不同的含义。

  • 负数代表正在进行初始化或扩容操作
  • -1代表正在初始化
  • -N 表示有N-1个线程正在进行扩容操作
  • 正数或0代表 Hash 表还没有被初始化

        concurrencyLevel 表示并发级别,这个值用来确定 Segment 的个数,Segment 的个数是大于等于 concurrencyLevel 的第一个2的n次方的数。默认值为 static final int DEFAULT_CONCURRENCY_LEVEL = 16;。理想情况下 ConcurrentHashMap 的真正的并发访问量能够达到 concurrencyLevel,因为有 concurrencyLevel个Segment,假如有 concurrencyLevel 个线程需要访问 Map,并且需要访问的数据都恰好分别落在不同的 Segment 中,则这些线程能够无竞争地自由访问,达到同时访问的效果。若 concurrencyLevel 超过最大值,则设置为最大值,size 属性也是这样的设定。

        TreeNode 树节点类,另外一个核心的数据结构。当链表长度过长的时候,会转换为 TreeNode。但是与 HashMap 不相同的是,它并不是直接转换为红黑树,而是把这些结点包装成 TreeNode 放在 TreeBin 对象中,由 TreeBin 完成对红黑树的包装。而且 TreeNode 在 ConcurrentHashMap 集成自 Node 类,而并非 HashMap 中的集成自 LinkedHashMap.Entry 类,也就是说 TreeNode 带有 next 指针,这样做的目的是方便基于 TreeBin 的访问。

        TreeBin 这个类并不负责包装用户的 key、value 信息,而是包装的很多 TreeNode 节点。它代替了 TreeNode 的根节点,也就是说在实际的 ConcurrentHashMap“数组”中,存放的是 TreeBin 对象,而不是 TreeNode 对象,这是与 HashMap 的区别。另外这个类还带有了读写锁。

        ForwardingNode 一个用于连接两个 table 的节点类。它包含一个nextTable指针,用于指向下一张表。而且这个节点的key value next指针全部为null,它的hash值为-1. 这里面定义的find的方法是从nextTable里进行查询节点,而不是以自身为头节点进行查找。

        在ConcurrentHashMap中,随处可以看到U,大量使用了U.compareAndSwapXXX的方法,这个方法是利用一个 CAS 算法实现无锁化的修改值的操作,我上面文章的算法是模拟 CAS 底层实现的例子。CAS底层通过反射字节码指令来实现大家可以看看下面的代码。

// concurrenthashmap 中CAS的代码private static final sun.misc.Unsafe U;private static final long SIZECTL;private static final long TRANSFERINDEX;private static final long BASECOUNT;private static final long CELLSBUSY;private static final long CELLVALUE;private static final long ABASE;private static final int ASHIFT;static {    try {        U = sun.misc.Unsafe.getUnsafe();        // 获取这个map的字节码文件,通过反射来获取各种属性内容        Class<?> k = ConcurrentHashMap.class;        // 获取一些重要属性        SIZECTL = U.objectFieldOffset            (k.getDeclaredField("sizeCtl"));        TRANSFERINDEX = U.objectFieldOffset            (k.getDeclaredField("transferIndex"));        BASECOUNT = U.objectFieldOffset            (k.getDeclaredField("baseCount"));        CELLSBUSY = U.objectFieldOffset            (k.getDeclaredField("cellsBusy"));        Class<?> ck = CounterCell.class;        CELLVALUE = U.objectFieldOffset            (ck.getDeclaredField("value"));        Class<?> ak = Node[].class;        ABASE = U.arrayBaseOffset(ak);        int scale = U.arrayIndexScale(ak);        if ((scale & (scale - 1)) != 0)            throw new Error("data type scale not a power of two");        ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);    } catch (Exception e) {        throw new Error(e);    }}

ConcurrentHashMap 中重要的方法代码解析:

    //PUT方法 JAVA7    V put(K key, int hash, V value, boolean onlyIfAbsent) {        lock();          //先加锁,put操作是在segment里面加锁的,也就是每个segment都可以加一把锁        try {            int c = count;            if (c++ > threshold) // ensure capacity                rehash();   //判断容量,如果不够了就扩容            HashEntry<K,V>[] tab = table;      //将table赋给一个局部变量tab,这是因为table是volatile变量,读写volatile变量的开销很大,编译器也不能对volatile变量的读写做任何优化,直接多次访问非volatile实例变量没有多大影响,编译器会做相应优化。            int index = hash & (tab.length - 1);   //寻找table的下标            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相同的为止,如果没找到,e指向链表尾            V oldValue;            if (e != null) { //如果有相同的key,那么直接替换                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();        }    }// PUT方法 (JAVA8)final V putVal(K key, V value, boolean onlyIfAbsent) {       // 若为空 抛异常     if (key == null || value == null) throw new NullPointerException();      // 计算hash值      int hash = spread(key.hashCode());      int binCount = 0;      for (Node<K,V>[] tab = table;;) {          Node<K,V> f; int n, i, fh;          //如果table为空的话,初始化table          if (tab == null || (n = tab.length) == 0)              tab = initTable();          //根据hash值计算出在table里面的位置           else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {              //如果这个位置没有值 ,直接放进去,不需要加锁              if (casTabAt(tab, i, null,                           new Node<K,V>(hash, key, value, null)))                  break;                   // no lock when adding to empty bin          }          //当遇到表连接点时,需要进行整合表的操作          else if ((fh = f.hash) == MOVED)              tab = helpTransfer(tab, f);          else {              V oldVal = null;              //结点上锁  这里的结点可以理解为hash值相同组成的链表的头结点              synchronized (f) {                  if (tabAt(tab, i) == f) {                      //fh〉0 说明这个节点是一个链表的节点 不是树的节点                      if (fh >= 0) {                          binCount = 1;                          //在这里遍历链表所有的结点                          for (Node<K,V> e = f;; ++binCount) {                              K ek;                              //如果hash值和key值相同  则修改对应结点的value值                              if (e.hash == hash &&                                  ((ek = e.key) == key ||                                   (ek != null && key.equals(ek)))) {                                  oldVal = e.val;                                  if (!onlyIfAbsent)                                      e.val = value;                                  break;                              }                              Node<K,V> pred = e;                              //如果遍历到了最后一个结点,那么就证明新的节点需要插入 就把它插入在链表尾部                              if ((e = e.next) == null) {                                  pred.next = new Node<K,V>(hash, key,                                                            value, null);                                  break;                              }                          }                      }                      //如果这个节点是树节点,就按照树的方式插入值                      else if (f instanceof TreeBin) {                          Node<K,V> p;                          binCount = 2;                          if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,                                                         value)) != null) {                              oldVal = p.val;                              if (!onlyIfAbsent)                                  p.val = value;                          }                      }                  }              }              if (binCount != 0) {                  //如果链表长度已经达到临界值8 就需要把链表转换为树结构                  if (binCount >= TREEIFY_THRESHOLD)                      treeifyBin(tab, i);                  if (oldVal != null)                      return oldVal;                  break;              }          }      }      //将当前ConcurrentHashMap的元素数量+1      addCount(1L, binCount);      return null;  }  // GET方法(JAVA7)V get(Object key, int hash) {    if (count != 0) {         HashEntry<K,V> e = getFirst(hash);   //寻找table的下标,也就是链表的表头        while (e != null) {            if (e.hash == hash && key.equals(e.key)) {                V v = e.value;                if (v != null)                    return v;                return readValueUnderLock(e); // 加锁读,这个加锁读不用重新计算位置,而是直接拿e的值            }            e = e.next;        }    }    return null;}// GET方法(JAVA8)public V get(Object key) {      Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;      //计算hash值      int h = spread(key.hashCode());      //根据hash值确定节点位置      if ((tab = table) != null && (n = tab.length) > 0 &&          (e = tabAt(tab, (n - 1) & h)) != null) {          //如果搜索到的节点key与传入的key相同且不为null,直接返回这个节点            if ((eh = e.hash) == h) {              if ((ek = e.key) == key || (ek != null && key.equals(ek)))                  return e.val;          }          //如果eh<0 说明这个节点在树上 直接寻找          else if (eh < 0)              return (p = e.find(h, key)) != null ? p.val : null;           //否则遍历链表 找到对应的值并返回          while ((e = e.next) != null) {              if (e.hash == h &&                  ((ek = e.key) == key || (ek != null && key.equals(ek))))                  return e.val;          }      }      return null;  }      // REMOVE (JAVA7)    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) {  //如果找到该key            V v = e.value;            if (value == null || value.equals(v)) {                oldValue = v;                ++modCount;                HashEntry<K,V> newFirst = e.next; //newFirst此时为要删除结点的next                for (HashEntry<K,V> p = first; p != e; p = p.next)                    newFirst = new HashEntry<K,V>(p.key,p.hash,newFirst, p.value);//从头遍历链表将要删除结点的前面所有结点复制一份插入到newFirst之前,如下图                tab[index] = newFirst;                count = c;             }        }        return oldValue;    } finally {        unlock();    }}// REMOVE OR REPLACE方法(JAVA8) final V replaceNode(Object key, V value, Object cv) {    int hash = spread(key.hashCode());    for (Node<K,V>[] tab = table;;) {        Node<K,V> f; int n, i, fh;        // 数组不为空,长度不为0,指定hash码值为0        if (tab == null || (n = tab.length) == 0 ||            (f = tabAt(tab, i = (n - 1) & hash)) == null)            break;        // 是一个 forwardNode        else if ((fh = f.hash) == MOVED)            tab = helpTransfer(tab, f);        else {            V oldVal = null;            boolean validated = false;            synchronized (f) {                if (tabAt(tab, i) == f) {                    if (fh >= 0) {                        validated = true;                        // 循环寻找                        for (Node<K,V> e = f, pred = null;;) {                            K ek;                            // equal 相同 取出                            if (e.hash == hash &&                                ((ek = e.key) == key ||                                 (ek != null && key.equals(ek)))) {                                V ev = e.val;                                 // value为null或value和查到的值相等                                  if (cv == null || cv == ev ||                                    (ev != null && cv.equals(ev))) {                                    oldVal = ev;                                    if (value != null)                                        e.val = value;                                    else if (pred != null)                                        pred.next = e.next;                                    else                                        setTabAt(tab, i, e.next);                                }                                break;                            }                            pred = e;                            if ((e = e.next) == null)                                break;                        }                    }                    // 若是树 红黑树高效查找/删除                    else if (f instanceof TreeBin) {                        validated = true;                        TreeBin<K,V> t = (TreeBin<K,V>)f;                        TreeNode<K,V> r, p;                        if ((r = t.root) != null &&                            (p = r.findTreeNode(hash, key, null)) != null) {                            V pv = p.val;                            if (cv == null || cv == pv ||                                (pv != null && cv.equals(pv))) {                                oldVal = pv;                                if (value != null)                                    p.val = value;                                else if (t.removeTreeNode(p))                                    setTabAt(tab, i, untreeify(t.first));                            }                        }                    }                }            }            if (validated) {                if (oldVal != null) {                    if (value == null)                        addCount(-1L, -1);                    return oldVal;                }                break;            }        }    }    return null;}

通过上面的对比大家应该很容易看出 JAVA8 和 JAVA7 对 ConcurrentHashMap 的实现改变,笔者更喜欢 CAS 无锁机制,如果只是看我写以上代码注释明显不足以了解 JAVA8 的 ConcurrentHashMap 的实现,我也仅仅提供源码阅读的思路,其中 cas、volatile、final 等注意已经给解释,所以如果大家真的感兴趣还是写程序,打断点,一步步看看这个代码的实现,相信会对大家以后并发编程有很好的帮助。

接下来,就是大家最期待的面试相关内容详解。

(1)你知道 HashMap 的工作原理吗?你知道 HashMap 的 get() 方法的工作原理吗?

HashMap 是基于 hashing 的原理,我们使用 put(key, value) 存储对象到 HashMap 中,使用 get(key) 从 HashMap 中获取对象。当我们给 put() 方法传递键和值时,我们先对键调用 hashCode() 方法,返回的 hashCode 用于找到 bucket 位置来储存 Entry 对象。

(2)你知道 ConcurrentHashMap 的工作原理吗?你知道 ConcurrentHashMap 在 JAVA8 和 JAVA7 对比有哪些不同呢?

ConcurrentHashMap 为了提高本身的并发能力,在内部采用了一个叫做 Segment 的结构,一个 Segment 其实就是一个类 Hash Table 的结构,Segment 内部维护了一个链表数组,我们用下面这一幅图来看下 ConcurrentHashMap 的内部结构,从下面的结构我们可以了解到,ConcurrentHashMap 定位一个元素的过程需要进行两次Hash操作,第一次 Hash 定位到 Segment,第二次 Hash 定位到元素所在的链表的头部,因此,这一种结构的带来的副作用是 Hash 的过程要比普通的 HashMap 要长,但是带来的好处是写操作的时候可以只对元素所在的 Segment 进行操作即可,不会影响到其他的 Segment,这样,在最理想的情况下,ConcurrentHashMap 可以最高同时支持 Segment 数量大小的写操作(刚好这些写操作都非常平均地分布在所有的 Segment上),所以,通过这一种结构,ConcurrentHashMap 的并发能力可以大大的提高。

   enter image description here

  
JAVA7之前ConcurrentHashMap主要采用锁机制,在对某个Segment进行操作时,将该Segment锁定,不允许对其进行非查询操作,而在JAVA8之后采用CAS无锁算法,这种乐观操作在完成前进行判断,如果符合预期结果才给予执行,对并发操作提供良好的优化

(3)当两个对象的hashcode相同会发生什么?

因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为Map使用LinkedList存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在LinkedList中。(当向 Map 中添加 key-value 对,由其 key 的 hashCode() 返回值决定该 key-value 对(就是 Entry 对象)的存储位置。当两个 Entry 对象的 key 的 hashCode() 返回值相同时,将由 key 通过 eqauls() 比较值决定是采用覆盖行为(返回 true),还是产生 Entry 链(返回 false)),此时若你能讲解JDK1.8红黑树引入,面试官或许会刮目相看。

(4)如果两个键的 hashcode 相同,你如何获取值对象?

当我们调用get()方法,HashMap 会使用键对象的 hashcode 找到 bucket 位置,然后获取值对象。如果有两个值对象储存在同一个 bucket,将会遍历 LinkedList 直到找到值对象。找到 bucket 位置之后,会调用 keys.equals() 方法去找到 LinkedList 中正确的节点,最终找到要找的值对象。(当程序通过 key 取出对应 value 时,系统只要先计算出该 key 的 hashCode() 返回值,在根据该 hashCode 返回值找出该 key 在 table 数组中的索引,然后取出该索引处的 Entry,最后返回该 key 对应的 value 即可)。

(5)如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

(6)你了解重新调整HashMap大小存在什么问题吗?

当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在LinkedList中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在LinkedList的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。这个时候,你可以质问面试官,为什么这么奇怪,要在多线程的环境下使用HashMap呢?

(7)请问ConcurrentHashMap中变量使用final和volatile修饰有什么用呢?其中链表是final的next属性,那么发生删除某个元素,如何实现的?

使用final来实现不变模式(immutable),他是多线程安全里最简单的一种保障方式。因为你拿他没有办法,想改变它也没有机会。不变模式主要通过final关键字来限定的。在JMM中final关键字还有特殊的语义。Final域使得确保初始化安全性(initialization safety)成为可能,初始化安全性让不可变形对象不需要同步就能自由地被访问和共享。

使用volatile来保证某个变量内存的改变对其他线程即时可见,在配合CAS可以实现不加锁对并发操作的支持

remove执行的开始就将table赋给一个局部变量tab,将tab依次复制出来,最后直到该删除位置,将指针指向下一个变量。

(8)描述一下ConcurrentHashMap中remove操作,有什么需要注意的?

需要注意如下几点。第一,当要删除的结点存在时,删除的最后一步操作要将count的值减一。这必须是最后一步操作,否则读取操作可能看不到之前对段所做的结构性修改。第二,remove执行的开始就将table赋给一个局部变量tab,这是因为table是volatile变量,读写volatile变量的开销很大。编译器也不能对volatile变量的读写做任何优化,直接多次访问非volatile实例变量没有多大影响,编译器会做相应优化。

(9)HashTable与ConcurrentHashMap有什么区别,描述锁分段技术。

HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。

大家有其他问题,我们之后讨论!!!

猜你喜欢

转载自blog.csdn.net/kangxidagege/article/details/81474704