Java基础
1.Java泛型了解吗?什么是类型擦除?介绍一下常用的通配符。
- Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
- Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。
- 常用的通配符为: T,E,K,V,?
? 表示不确定的 java 类型
T (type) 表示具体的一个java类型
K V (key value) 分别代表java键值中的Key Value
E (element) 代表Element
2.线程有哪些基本状态。
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态
通过Thread类中的State枚举类型可以看出来。
1.初始状态(NEW)线程被构建但是还没有调用start()方法;
2.运行状态(RUNNABLE)Java线程将操作系统中的就绪和运行统称为“运行中”;
3.阻塞状态(BLOCKED)表示线程阻塞与锁;
4.等待状态(WAITING)表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或者中断);
5.超时等待状态(TIME_WAITING)该状态不同于WAITING,他可以在指定的时间自行返回;
6.终止状态(TERMINATED)表示当前线程已经执行完完毕。
3.BIO,NIO,AIO有什么区别。
- BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
- NIO (Non-blocking/New I/O): NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
- AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
容器
1.什么是迭代器Iterator
提供一种访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节,主要的方法有三个hasNext() 和 next()以及remove()方法,同时迭代器还维持了集合的安全性因为在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。只能使用迭代器中的remove()方法进行删除。
2.如果你要使用线程安全的集合的话, java.util.concurrent 包中提供了很多并发容器供你使用:
ConcurrentHashMap: 可以看作是线程安全的 HashMap
CopyOnWriteArrayList:可以看作是线程安全的 ArrayList,在读多写少的场合性能非常好,远远好于 Vector.
ConcurrentLinkedQueue:高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。
BlockingQueue: 这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。
ConcurrentSkipListMap :跳表的实现。这是一个Map,使用跳表的数据结构进行快速查找。
3.ArrayList扩容机制
当初始化的时候ArrayList大小为0,当执行add方法的时候集合扩容为10,当集合中元素个数超过10则每次按照1.5倍进行扩容。
4.HashMap长度为什么是2的幂次方
为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648 到 2147483647,前后加起来大概 40 亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个 40 亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ (n - 1) & hash”。(n 代表数组长度)。这也就解释了 HashMap 的长度为什么是 2 的幂次方。
5.ConcurrentHashMap和HashMap的区别
实现线程安全的方式(重要): ① 在 JDK1.7 的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 到了 JDK1.8 的时候已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6 以后 对 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
6.什么是快速失败(fail-fast)
快速失败(fail-fast) 是 Java 集合的一种错误检测机制。在使用迭代器对集合进行遍历的时候,我们在多线程下操作非安全失败(fail-safe)的集合类可能就会触发 fail-fast 机制,导致抛出 ConcurrentModificationException 异常。 另外,在单线程下,如果在遍历过程中对集合对象的内容进行了修改的话也会触发 fail-fast 机制。
7.什么是安全失败(fail-safe)
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。所以,在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛 ConcurrentModificationException 异常。