Java面试相关

Java面试相关

2018-01-22  本文已影响0人  jackybao
1. HashMap的工作原理是什么?
在 Java8 之前, HashMap 是链表散列的数据结构,即数组和链表的结合体
从 Java8 开始,引入红黑树的数据结构和扩容的优化

HashMap 使用哈希表来存储。哈希表为解决冲突,可以采用开放地址法和链地址法等来解决问题
Java 中 HashMap 采用了链地址法。
链地址法,就是数组加链表的结合。
在每个数组元素上都一个链表结构,当数据被Hash后,得到数组下标,把数据放在对应下标元素的链表上。

当我们往 HashMap 中 put 元素的时候,先根据 key 的 hashCode 重新计算 hash 值,
根据 hash 值再通过高位运算和取模运算得到这个元素在数组中的位置(即下标),
如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,
新加入的放在链头,最先加入的放在链尾,
如果该链表超出 8 个的话,就转换成红黑树。
如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

定位位置的方法通过以上三个步骤得到,取 key 的 hashCode 的值,
然后进行无符号右移 16 位,再与现有哈希桶数组的大小取模。
通过 (n - 1) & hash 来得到该对象的保存位求得这个位置的时候,
马上就可以知道对应位置的元素,不用遍历链表,大大优化了查询的效率。

当 length 总是 2 的 n 次方时,(length - 1) & hash 运算等价于对 length 取模,
也就是h % length,但是 & 比 % 具有更高的效率。

2. HashMap与HashTable的区别是什么?
1. HashTable基于Dictionary类,而HashMap是基于AbstractMap
   Dictionary是任何可将键映射到相应值的类的抽象父类
   AbstractMap是基于Map接口的实现,它以最大限度地减少实现此接口所需的工作
2. HashMap的key和value都允许为null
   Hashtable的key和value都不允许为null
   HashMap遇到key为null的时候,调用putForNullKey方法进行处理,而对value没有处理
   Hashtable遇到null,直接返回NullPointerException
3. Hashtable是线程安全的,而HashMap是非线程安全的,
   可以通过Collections.synchronizedMap(hashMap),使其实现线程安全
3. CorrentHashMap的工作原理?
ConcurrentHashMap采用一种乐观锁CAS算法来实现同步问题
其底层还是 数组+链表->红黑树 的实现

CAS算法
一个CAS方法包含三个参数CAS(V,E,N)。V表示要更新的变量,E表示预期的值,N表示新值。
只有当V的值等于E时,才会将V的值修改为N。如果V的值不等于E,说明已经被其他线程修改了,
当前线程可以放弃此操作,也可以再次尝试次操作直至修改成功。
基于这样的算法,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰(临界区值的修改),并进行恰当的处理。
4. HashSet的底层实现是什么?
HashSet的实现是依赖于HashMap的,HashSet的值都是存储在HashMap中的。
在HashSet的构造法中会初始化一个HashMap对象,HashSet不允许值重复,
因此HashSet的值是作为HashMap的key存储在HashMap中的,当存储的值已经存在时返回false
5. Java 类的初始化顺序
1. 父类的静态变量/静态初始化块
2. 子类类的静态变量/静态初始化块
3. 父类的动态初始化块(与静态初始化块类似,只是没有static关键字,即放在一对大括号中的代码块)、非构造方法和set方法的成员变量初始化
4. 父类的构造方法
5. 子类的动态初始化块、非构造方法和set方法的成员变量初始化
6. 子类的构造方法
7. 父类本地变量
8. 子类的本地变量
6. 线程池如何调优
调整线程池的大小,线程池的最佳大小取决于可用处理器的数目以及工作队列中的任务的性质。
一般需要根据任务的类型来配置线程池大小:
如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为NCPU+1;
如果是IO密集型任务,参考值可以设为2NCPU。
7. 线程池的种类、区别和使用场景
Java通过Executors提供四种线程池,分别为:

newCachedThreadPool 
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,
若无可回收,则新建线程。

newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,
保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

newSingleThreadScheduledExecutor
线程池中只有一个工作线程,它能按时间计划来执行任务。
8. 分析线程池的实现原理和线程的调度过程
线程池原理:预先启动一些线程,线程无限循环从任务队列中获取一个任务进行执行,直到线程池被关闭。
如果某个线程因为执行某个任务发生异常而终止,那么重新创建一个新的线程而已,如此反复。
一个线程池包括以下四个基本部分:

线程池管理器(ThreadPool):
用于创建并管理线程池,包括创建线程池、销毁线程池、添加新任务。

工作线程(PoolWorker):
我们把用来执行用户任务的线程称为工作线程,工作线程就是不断从队列中获取任务对象并执行对象上的业务方法。
线程池中的线程,在没有任务时处于等待状态,可以循环的执行任务。

任务接口(Task):
每个任务必须实现的接口,以供工作线程调度任务的执行,
它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等。

任务队列(TaskQueue):
用于存放没有处理的任务。提供一种缓冲机制。
9. 对ThreadLocal的理解
ThreadLocal是一个创建线程局部变量的类。
通常情况下我们创建的变量,可以被多个线程访问并修改,
通过ThreadLocal创建的变量只能被当前线程访问。

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocal的值是放入了当前线程的一个ThreadLocalMap实例中,
所以只能在本线程中访问,其他线程无法访问。 

ThreadLocal并不会导致内存泄露
因为ThreadLocalMap中的key存储的是ThreadLocal实例的弱引用,
因此如果应用使用了线程池,即便之前的线程实例处理完之后出于复用的目的依然存活,
也不会产生内存泄露。
10. 垃圾搜集器
按照线程数量来分:
串行 串行垃圾回收器一次只使用一个线程进行垃圾回收
并行 并行垃圾回收器一次将开启多个线程同时进行垃圾回收

按照工作模式来分:
并发 并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间
独占 一旦运行,就停止应用程序中的其他所有线程,直到垃圾回收过程完全结束

按照碎片处理方式:
压缩式 压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整消除回收后的碎片
非压缩式 非压缩式的垃圾回收器不进行这步操作

按工作的内存区间 可分为新生代垃圾回收器和老年代垃圾回收器

新生代串行收集器 serial 
它仅仅使用单线程进行垃圾回收;第二,它独占式的垃圾回收。使用复制算法。

老年代串行收集器 serial old 
老年代串行收集器使用的是标记-压缩算法。
和新生代串行收集器一样,它也是一个串行的、独占式的垃圾回收器

并行收集器 parnew 
并行收集器是工作在新生代的垃圾收集器,它只简单地将串行回收器多线程化。
它的回收策略、算法以及参数和串行回收器一样 并行回收器也是独占式的回收器,在收集过程中,应用程序会全部暂停。
但由于并行回收器使用多线程进行垃圾回收,
因此,在并发能力比较强的 CPU 上,它产生的停顿时间要短于串行回收器,
而在单 CPU 或者并发能力较弱的系统中,并行回收器的效果不会比串行回收器好,
由于多线程的压力,它的实际表现很可能比串行回收器差。

新生代并行回收 (Parallel Scavenge) 收集器 
新生代并行回收收集器也是使用复制算法的收集器。
从表面上看,它和并行收集器一样都是多线程、独占式的收集器。
但是,并行回收收集器有一个重要的特点:它非常关注系统的吞吐量。

老年代并行回收收集器 parallel old 
老年代的并行回收收集器也是一种多线程并发的收集器。
和新生代并行回收收集器一样,它也是一种关注吞吐量的收集器。
老年代并行回收收集器使用标记-压缩算法,JDK1.6 之后开始启用。

CMS 收集器 CMS 收集器主要关注于系统停顿时间。
CMS 是 Concurrent Mark Sweep 的缩写,意为并发标记清除,从名称上可以得知,
它使用的是标记-清除算法,同时它又是一个使用多线程并发回收的垃圾收集器。
CMS 工作时,主要步骤有:初始标记、并发标记、重新标记、并发清除和并发重置。
其中初始标记和重新标记是独占系统资源的,而并发标记、并发清除和并发重置是可以和用户线程一起执行的。
因此,从整体上来说,CMS 收集不是独占式的,它可以在应用程序运行过程中进行垃圾回收。
根据标记-清除算法,初始标记、并发标记和重新标记都是为了标记出需要回收的对象。
并发清理则是在标记完成后,正式回收垃圾对象;并发重置是指在垃圾回收完成后,重新初始化 CMS 数据结构和数据,为下一次垃圾回收做好准备。
并发标记、并发清理和并发重置都是可以和应用程序线程一起执行的。

G1 收集器 G1 收集器是基于标记-压缩算法的。
因此,它不会产生空间碎片,也没有必要在收集完成后,进行一次独占式的碎片整理工作。
G1 收集器还可以进行非常精确的停顿控制。
上一篇下一篇

猜你喜欢

热点阅读