11. HashMap和ConcurrentHashMap的区别

 
从JDK1.2起,就有了HashMap,正如上一个问题所提到的,HashMap与HashTable不同,不是线程安全的,因此多线程操作时需要格外小心。
在JDK1.5中,伟大的Doug Lea给我们带来了concurrent包,从此我们有线程安全的ConcurrentHashMap用了。
那么ConcurrentHashMap是怎么实现线程安全的呢?肯定不可能是每个方法都加上synchronized关键字,否则就和HashTable一样了,我们来看看ConcurrentHashMap的put方法:
  1. public V put(K key, V value) {
  2. return putVal(key, value, false);
  3. }
 
调用了putVal方法,我们继续看看putVal方法的源码。
  1. final V putVal(K key, V value, boolean onlyIfAbsent) {
  2. if (key == null || value == null) throw new NullPointerException();
  3. int hash = spread(key.hashCode());
  4. int binCount = 0;
  5. for (Node<K,V>[] tab = table;;) {
  6. Node<K,V> f; int n, i, fh;
  7. if (tab == null || (n = tab.length) == 0)
  8. tab = initTable();
  9. else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
  10. if (casTabAt(tab, i, null,
  11. new Node<K,V>(hash, key, value, null)))
  12. break;                   // no lock when adding to empty bin
  13. }
  14. else if ((fh = f.hash) == MOVED)
  15. tab = helpTransfer(tab, f);
  16. else {
  17. V oldVal = null;
  18. synchronized (f) {
  19. if (tabAt(tab, i) == f) {
  20. if (fh >= 0) {
  21. binCount = 1;
  22. for (Node<K,V> e = f;; ++binCount) {
  23. K ek;
  24. if (e.hash == hash &&
  25. ((ek = e.key) == key ||
  26. (ek != null && key.equals(ek)))) {
  27. oldVal = e.val;
  28. if (!onlyIfAbsent)
  29. e.val = value;
  30. break;
  31. }
  32. Node<K,V> pred = e;
  33. if ((e = e.next) == null) {
  34. pred.next = new Node<K,V>(hash, key,
  35. value, null);
  36. break;
  37. }
  38. }
  39. }
  40. else if (f instanceof TreeBin) {
  41. Node<K,V> p;
  42. binCount = 2;
  43. if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
  44. value)) != null) {
  45. oldVal = p.val;
  46. if (!onlyIfAbsent)
  47. p.val = value;
  48. }
  49. }
  50. }
  51. }
  52. if (binCount != 0) {
  53. if (binCount >= TREEIFY_THRESHOLD)
  54. treeifyBin(tab, i);
  55. if (oldVal != null)
  56. return oldVal;
  57. break;
  58. }
  59. }
  60. }
  61. addCount(1L, binCount);
  62. return null;
  63. }

来看一些关键的代码:在第2行,方法检测了key和value是否为空,如果为空则抛出NullPointerException(看来线程安全的key和value都必须非空,和HashTable一样)。然后在第9行构造了一个Node对象,这个对象代表的是键值对节点(内部有next指针指向下一个节点)的实体。在ConcurrentHashMap内部维护了一个Node对象的数组,它大小是2的指数,且是volatile的具有原子可见性,数组索引是key经过哈希函数得出的哈希值:

  1. /**
  2. * The array of bins. Lazily initialized upon first insertion.
  3. * Size is always a power of two. Accessed directly by iterators.
  4. */
  5. transient volatile Node<K,V>[] table;

看回putVal方法,从第12行的注释我们可以看出,如果是往一个空的索引位置放入一个新的Node节点,则不需要加锁。再看到方法第18行,我们发现这里有个临界区,此时处理的是往一个已有节点的索引位置加入新的节点情况,那么在链表完成之前很明显我们不应该让其他新节点干扰我们的工作,因此此处为索引头的Node对象加了锁,但此时别的索引位置是不加锁的。
看完了put方法,我们再来看看get方法:

  1. public V get(Object key) {
  2. Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
  3. int h = spread(key.hashCode());
  4. if ((tab = table) != null && (n = tab.length) > 0 &&
  5. (e = tabAt(tab, (n - 1) & h)) != null) {
  6. if ((eh = e.hash) == h) {
  7. if ((ek = e.key) == key || (ek != null && key.equals(ek)))
  8. return e.val;
  9. }
  10. else if (eh < 0)
  11. return (p = e.find(h, key)) != null ? p.val : null;
  12. while ((e = e.next) != null) {
  13. if (e.hash == h &&
  14. ((ek = e.key) == key || (ek != null && key.equals(ek))))
  15. return e.val;
  16. }
  17. }
  18. return null;
  19. }

可以看到由于volatile关键字保证了原子可见性,get方法是完全没有加锁的。

对于remove方法和put方法类似,都是为要操作的索引头Node对象加锁构造临界区,此处不再贴出代码赘述。
 
综上所述,HashMap和ConcurrentHashMap区别如下:
  1. HashMap允许key和value为空值,ConcurrentHashMap不允许
  2. HashMap的put和remove不加锁,不是线程安全的,而ConcurrentHashMap加锁,是线程安全的
  3. 两者的get方法都没有加锁,但HashMap的Node数组不具有volatile关键字
补充:本人的JDK版本为1.7,网上找的大部分资料都介绍ConcurrentHashMap使用了分段锁,但从源码来看这种方法在1.7貌似已经弃用了(put方法不一样了):
  1. /**
  2. * Stripped-down version of helper class used in previous version,
  3. * declared for the sake of serialization compatibility
  4. */
  5. static class Segment<K,V> extends ReentrantLock implements Serializable {
  6. private static final long serialVersionUID = 2249069246763182397L;
  7. final float loadFactor;
  8. Segment(float lf) { this.loadFactor = lf; }
  9. }

12. TreeMap、HashMap、LinkedHashMap的区别

 
HashMap就不说了,一个最常用的Map,前面几个问题都提及了。

LinkedHashMap:

LinkedHashMap是HashMap的子类,其类的声明如下:
  1. public class LinkedHashMap<K,V>
  2. extends HashMap<K,V>
  3. implements Map<K,V>

其与HashMap最大的不同在于内部维护了一个用于遍历的双向链表,遍历的时候能够保持元素插入的顺序:

  1. /**
  2. * The head (eldest) of the doubly linked list.
  3. */
  4. transient LinkedHashMap.Entry<K,V> head;
  5. /**
  6. * The tail (youngest) of the doubly linked list.
  7. */
  8. transient LinkedHashMap.Entry<K,V> tail;

TreeMap:

TreeMap内部使用红黑树实现,因此插入TreeMap内部的元素遍历时是有序的:
  1. /**
  2. * The comparator used to maintain order in this tree map, or
  3. * null if it uses the natural ordering of its keys.
  4. *
  5. * @serial
  6. */
  7. private final Comparator<? super K> comparator;
  8. private transient Entry<K,V> root;

13. Collection包结构,与Collections的区别

Collection即Java中的容器类,其包结构如下所示:

Collection:

可以看到Java容器中有Collection和Map两种类型,Collection表示元素的集合,Map表示键值对映射的集合,Map中也包含了Collection(key的集合,value的集合以及键值对的集合),而Collection又可以返回Iterator(迭代器)用于容器的遍历和访问。Collection下又细分为多种特点不同的容器,主要有:
  1. 按元素插入顺序存储的List
  2. 不允许元素重复的Set
  3. 先进先出的Queue
  4. 后进先出的Stack
每种容器又有对应的更细节的实现。一般我们创造新容器时不需要继承Collection类,继承Collection子类下的容器抽象类即可。
 

Collections:

除了Collection类以外,可以看到图的右下角还有两个工具类,分别是Collections和Arrays。两个类的构造方法均为private,及不允许新建该类的实例,同时两个类包含了大量的静态方法用于处理我们的存储结构(排序,二分查找,填充等操作)。其中,Collections用于处理Collection容器类的存储结构,而Arrays则用于处理基本类型组成的数组。
 

14. try catch finally,try里有return,finally还执行么?

执行,无论发生啥情况,try后面的finally中的代码块必定会执行,示例:
  1. public class Test {
  2. public static void main(String[] args) {
  3. try {
  4. System.out.println("try");
  5. return;
  6. } catch (Exception e) {
  7. } finally {
  8. System.out.println("finally");
  9. }
  10. }
  11. }

结果:

 
 

15. Excption与Error包结构,OOM遇到的情况

Java中有关于异常类的结构图如下:
在Java中,异常的根类是java.lang.Throwable类,而根类又分为两大类:Error和Exception:
  • Error是无法处理的异常,比如OutOfMemoryError,一般发生这种异常,JVM会选择终止程序。因此我们编写程序时不需要关心这类异常。
  • Exception,也就是我们经常见到的一些异常情况,比如NullPointerException、IndexOutOfBoundsException,这些异常是我们可以处理的异常。
其中,Exception类又细分为checked exception和unchecked exception(也称RuntimeException运行时异常)
对于unchecked exception(非检查异常),也称运行时异常(RuntimeException),比如常见的NullPointerException、IndexOutOfBoundsException,java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定。(可以处理,也可以不处理)
对于checked exception(检查异常),也称非运行时异常(运行时异常以外的异常就是非运行时异常),java编译器强制程序员必须进行捕获处理,比如常见的IOExeption和SQLException。对于非运行时异常如果不进行捕获或者抛出声明处理,编译都不会通过。

关于OOM:

OOM即out of memory,内存溢出,与JVM的运行时内存有关,当JVM内存不够时就会发生OOM,主要分一下几种情况:
    1. 堆溢出:堆是JVM存放对象实例的地方,如果我们产生的对象过多,JVM又没有及时的GC,就会突破最大堆容量限制从而发生OOM
    2. 操作栈或本地方法栈溢出:如果线程在拓展栈时无法申请到足够的内存,也会发生OOM
    3. 方法区溢出:方法区存储了JVM中的常量,静态变量和类信息等信息,一个类如果要被垃圾收集器回收,判定条件是很苛刻的,因此在经常动态生成大量Class也可能发生OOM

关于一些基础的Java问题的解答(三)的更多相关文章

  1. 关于一些基础的Java问题的解答(一)

    学习一门语言基础是非常重要的,因此本文总结了一些常见的Java基础问题的解答,希望可以帮到大家. 1. 九种基本数据类型的大小,以及他们的封装类. 9种基本数据类型 基本类型 包装类型 大小 bool ...

  2. 关于一些基础的Java问题的解答(七)

    31. 反射的作用与原理 简单的来说,反射机制其实就是指程序在运行的时候能够获取自身的信息.如果知道一个类的名称或者它的一个实例对象, 就能把这个类的所有方法和变量的信息(方法名,变量名,方法,修饰符 ...

  3. 关于一些基础的Java问题的解答(六)

    26. ThreadPool用法与优势 ThreadPool即线程池,它是JDK1.5引入的Concurrent包中用于处理并发编程的工具.使用线程池有如下好处: 降低资源消耗:通过重复利用已创建的线 ...

  4. 关于一些基础的Java问题的解答(四)

    16. Java面向对象的三个特征与含义 java中的面向对象的三大基本特征分别是:封装.继承.多态: 封装:把过程和数据包围起来,对数据的访问只能通过已定义的界面,主要是方便类的修改 继承:对象的一 ...

  5. 关于一些基础的Java问题的解答(五)

    21. 实现多线程的两种方法:Thread与Runable 在Java中实现多线程编程有以下几个方法: 1.继承Thread类,重写run方法 public class Test { public s ...

  6. 关于一些基础的Java问题的解答(二)

    6. Hashcode的作用 官方对于hashCode的解释如下: Whenever it is invoked on the same object more than once during an ...

  7. 黑马程序员:Java基础总结----java注解

    黑马程序员:Java基础总结 java注解   ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! java注解 lang包中的基本注解 @SuppressWarnings ...

  8. java面试题—精选30道Java笔试题解答(二)

    摘要: java面试题-精选30道Java笔试题解答(二) 19. 下面程序能正常运行吗() public class NULL { public static void haha(){ System ...

  9. Java基础:Java的四种引用

    在Java基础:java虚拟机(JVM)中,我们提到了Java的四种引用.包括:强引用,软引用,弱引用,虚引用.这篇博客将详细的讲解一下这四种引用. 1. 强引用 2. 软引用 3. 弱引用 4. 虚 ...

随机推荐

  1. unity A*寻路 (三)A*算法

    这里我就不解释A*算法 如果你还不知道A*算法 网上有很多简单易懂的例子 我发几个我看过的链接 http://www.cnblogs.com/lipan/archive/2010/07/01/1769 ...

  2. maven入门(9)Maven常用命令

    Maven常用命令 清理 clean编译 compile打包 package安装 install跳过测试 clean package -Dmaven.test.skip=true

  3. js常用的数组方法

    1.创建数组的基本方法:  1.1 空数组  var obj=new Array();                 1.2 指定长度数组  var obj=new Array(size);     ...

  4. 使用新一代js模板引擎NornJ提升React.js开发体验

    当前的前端世界中有很多著名的开源javascript模板引擎如Handlebars.Nunjucks.EJS等等,相信很多人对它们都并不陌生. js模板引擎的现状 通常来讲,这些js模板引擎项目都有一 ...

  5. Java:Java 中会存在内存泄漏吗

    理论上Java因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是Java被广泛使用于服务器端编程的一个重要原因):然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致 ...

  6. [转]pycharm常用快捷键及设置

    PyCharm3.0默认快捷键(翻译的) PyCharm Default Keymap 1.编辑(Editing) Ctrl + Space    基本的代码完成(类.方法.属性)Ctrl + Alt ...

  7. 【Web安全】DoS及其家族

    不久前我分享过的Web安全概述获得了大家的广泛关注,说明大家对Web安全这一块还是很关心的,因此木可大大将陆续推出目前常见的Web攻击手段和对应的防范策略.本期向大家介绍的是DoS和它的家族. DoS ...

  8. linux(centos)常用命令

    原文:https://blog.csdn.net/zhangzhikaixinya/article/details/44538571 1.查看当前所在路径:pwd 2.新建文件夹www:mkdir w ...

  9. lsdyna进阶教程-弹性球撞击刚性平板

    在lsdyna基础教程中,我们做了一个关于刚性球撞击弹性平板的粒子,现在我们考虑另外一个问题,如果用弹性球撞击刚性地面该怎么做呢?是不是要从头开始建模,操作步骤是不是一样呢?其实很简单,将球和地面的材 ...

  10. 探寻 webpack 插件机制

    webpack 可谓是让人欣喜又让人忧,功能强大但需要一定的学习成本.在探寻 webpack 插件机制前,首先需要了解一件有意思的事情,webpack 插件机制是整个 webpack 工具的骨架,而 ...