HashMap、ConcurentHashMap的原理与实现(持续补充)+ (一些常见面试题)

一、hashmap:

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

  • JDK1.7和JDK1.8中HashMap的区别:

JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间

  • HashMap中的indexFor方法分析

indexFor(int h, int length) 方法来计算Entry对象保存在 table中的数组索引值:
HashMap的初始容量和扩容都是以2的次方来进行的,那么length-1换算成二进制的话肯定所有位都为1,就比如2的3次方为8,length-1的二进制表示就是111, 而按位与计算的原则是两位同时为“1”,结果才为“1”,否则为“0”。所以h& (length-1)运算从数值上来讲其实等价于对length取模,也就是h%length

位运算:index= HashCode(Key) & (Length - 1)

哈希冲突时插入新的值运用头插法,也就是说数组中的值是链表(头节点)中最新插入的值,之所以放在头节点,是因为HashMap的发明者认为,后插入的Entry被查找的可能性更大

二、ConcurentHashMap

  • ConcurentHashMap能够实现线程安全的原理

答:采用锁分段技术,一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构,同时segment继承了可重入锁ReenTranLock; 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护着一个HashEntry数组元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁,这样多个线程就不用使用同一把锁了,默认支持16个线程并发操作ConcurentHashMap。

  • ConcurentHashMap的put操作?

Put方法首先根据key的hashCode,再使用Wang/Jenkins hash的变种算法对元素的hashCode进行一次再哈希定位到Segment,然后在Segment里进行插入操作。插入操作需要经历两个步骤,第一步判断是否需要对Segment里的HashEntry数组进行扩容,第二步定位添加元素的位置然后放在HashEntry数组里。

是否需要扩容。在插入元素前会先判断Segment里的HashEntry数组是否超过容量(threshold),如果超过阀值,数组进行扩容。

如何扩容。扩容的时候首先会创建一个两倍于原容量的数组,然后将原数组里的元素进行再hash后插入到新的数组里。为了高效ConcurrentHashMap不会对整个容器进行扩容,而只对某个segment进行扩容。

我们知道 HashTable的get的操作时要加锁的,那么concurentHashMap的get操作加锁吗?是怎么实现的?

get的整个过程是不加锁的。
后续补充剩下的~~~~~~
################################################################

  • bio和nio有什么区别?(额外补充)

bio是同步阻塞,nio是同步非阻塞

同步调用,比如client调用server,在server返回结果之前,client的代码不会往下执行,异步就反过来,server没有返回结果,client可以继续往下执行;而非阻塞体现通道对象调用读方法和写方法时,没有如果没有可读和可写的数据时,立刻返回,我们可以把这件事记下来,记录的方式通常是在Selector(通道管理器 )上注册标记位,然后切换到其它就绪的连接(channel)继续进行读写,阻塞就反过来,如果数据不可读,不可写,不能立刻返回,线程只能干等。

  • java静态代理和动态代理的区别?

静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类文件就已经存在了。

动态代理:在程序运行时,运用反射机制动态创建而成。
Java的Executors提供了什么线程池?

  • Java通过Executors提供四种线程池

分别为:

  1. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool 创建一个定长计划线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
  • ThreadLocal

threadLocal是线程安全同步类,从共享变量中复制一份副本,存储到当前线程ThreadLocalMap类型的ThreadLocal中,这样这个每个线程都会使用自己的数据,多个线程不操作共享数据,也就不会出现线程安全问题了,是属于一种空间换时间做法。

  • ThreadLocal中,每个线程的副本存储在哪里?

Thread类中有一个应用变量ThreadLocals,是属于ThreadLocalMap类型,key是ThreadLocal对象,value就是线程要独享的值,每个线程的副本就存储在这里。

  • ThreadLocal是怎么获得副本的?

可以从ThreadLocal的get函数中看出来,首先是调用getMap(Thread T)方法,从当前线程中取出类型为ThreadLocalMap的threadLocals变量,然后再根据当前threadLocal对象取出value(副本)

发布了4 篇原创文章 · 获赞 4 · 访问量 244

猜你喜欢

转载自blog.csdn.net/qq_41065910/article/details/104666749