1.int Integer自动装箱与拆箱
装箱
Integer i = 10;
拆箱
int n = i; //拆箱
装箱就是 自动将基本数据类型转换为包装类型;拆箱就是 自动将包装类型转换为基本数据类型。
装箱和拆箱是如何实现的
在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。
2.代码输出结果
public
class
Main {
public
static
void
main(String[] args) {
Integer i1 =
100
;
Integer i2 =
100
;
Integer i3 =
200
;
Integer i4 =
200
;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
输出结果为:true false
在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
3.Java类中不同代码块的执行顺序
4.栈(stack)、堆(heap)和方法区
String str = new String("hello");
变量str放在栈上,用new创建出来的字符串对象放在堆上,而"hello"这个字面量是放在方法区的。
5.锁
偏向锁/轻量级锁/重量级锁
这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。
偏向锁:是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
轻量级锁:是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁:是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
6.乐观锁
一般来说有以下2种方式:
版本号机制:
一般是在数据表中加上版本号字段 version
,表示数据被修改的次数。当数据被修改时,这个字段值会加1。最后比较提交的数据版本号是否大于当前的数据版本号。
“提交版本必须大于记录当前版本才能执行更新” 的乐观锁策略。
CAS 算法:
即 compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS 算法涉及到三个操作数:
需要读写的内存值 V
进行比较的值 A
拟写入的新值 B
当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作(比较和替换是一个 native 原子操作)。一般情况下,这是一个自旋操作,即不断的重试。
7.lock和synchronized区别及应用场景
synchronized底层:其实synchronized映射成字节码指令就是增加来两个指令:monitorenter和monitorexit。当一条线程遇到monitorenter指令的时候,它会去尝试获得锁,如果获得锁那么锁计数器+1,如果没有获得锁,那么阻塞。当它遇到monitorexit的时候,锁计数器-1,当计数器为0,那么就释放锁。
lock底层:
Lock锁的底层实现是AQS
AQS:抽象的队列式同步器
2.AQS 的核心思想
如果被请求资源空闲,则将当前线程设置为有效的工作线程,并将资源设置为锁定状态,如果被请求资源被占用,那么就需要线程阻塞及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁(CLH 锁是一个自旋锁,提供先来先服务的公平性)实现的,即将暂时获取不到锁的线程加入到队列中。
8.线程池
1.优势:
降低资源消耗
提高响应速度
提高线程的可管理性
2.线程池的真正实现类是 ThreadPoolExecutor
3.参数
核心线程数
程池所能容纳的最大线程数
线程闲置超时时长
线程闲置超时时长单位
任务队列
(可选):线程工厂
(可选):拒绝策略
4. Executors 框架已经为我们实现了 4 种拒绝策略:
AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
CallerRunsPolicy:由调用线程处理该任务。
DiscardPolicy:丢弃任务,但是不抛出异常。
DiscardOldestPolicy:丢弃队列最早的未处理任务
5.Executors已经为我们封装好了 4 种常见的功能线程池:
- 定长线程池(FixedThreadPool)
- 定时线程池(ScheduledThreadPool )
- 可缓存线程池(CachedThreadPool)
- 单线程化线程池(SingleThreadExecutor)
9.volatile和synchronized的区别
- volatile告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量
- volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
- volatile仅能实现可见性,不能保证原子性;而synchronized则可以保证可见性和原子性
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
- volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
10.HashMap
1.数据结构
HashMap 数据结构为 数组+链表,其中:链表的节点存储的是一个 Entry 对象,每个Entry 对象存储四个属性(hash,key,value,next)
2.容器默认的数组大小为 16,加载因子 为0.75。容器的阈(yu)值为 initialCapacity * loadFactor,默认情况下阈值为 16 * 0.75 = 12;
3.在JDK1.7时HashMap采取的是数组+链表的形式,JDK1.8对HashMap引入了红黑树数据结构,极大的增强了HashMap的存取性能!
为什么会引入红黑树呢?因为HashMap存在一个问题,即使负载因子和Hash算法设计的再合理,也无法避免出现在链表上拉链过长的问题,如果极端情况下出现严重的Hash冲突,会严重影响HashMap的存取性能,于是HashMap在jdk1.8时,引入了红黑树,利用红黑树快速增删改查的特点来优化了HashMap的性能!
4.HashMap之put方法
第一步:判断键值对数组是否为空,是则执行resize()扩容。
第二步:根据键key计算hash值得到插入数组的索引i,如果tab[i]== null则直接插入,执行第六步;如果tab[i] != null,执行第三步。
第三步:判断tab[i]的第一个元素与插入元素key的hashcode&equals是否相等,相等则覆盖,否则执行第四步。
第四步:判断tab[i]是否是红黑树节点TreeNode,是则在红黑树中插入节点,否则执行第五步。
第五步:遍历tab[i]判断链表是否大于8,大于8则可能转成红黑树(数组需要大于64),满足则在红黑树中插入节点;否则在链表中插入;在遍历链表的过程中如果存在key的hashcode&equals相等则替换即可。
第六步:插入成功,判断hashmap的size是否超过threshold的值,超过则扩容。
5.为什么要改成“数组+链表+红黑树”?
为了提升在 hash 冲突严重时(链表过长)的查找性能,使用链表的查找性能是 O(n),而使用红黑树是 O(logn)。
6.什么时候用链表?什么时候用红黑树?
默认情况下是使用链表节点。当同一个索引位置的节点在新增后达到9个(阈值8):如果此时数组长度大于等于 64,则会触发链表节点转红黑树节点;而如果数组长度小于64,则不会触发链表转红黑树,而是会进行扩容,因为此时的数据量还比较小。
7.HashMap 有哪些重要属性?分别用于做什么的?
除了用来存储我们的节点 table 数组外,HashMap 还有以下几个重要属性:
1)size:HashMap 已经存储的节点个数;
2)threshold:扩容阈值,当 HashMap 的个数达到该值,触发扩容。
3)loadFactor:负载因子,扩容阈值 = 容量 * 负载因子。
8.HashMap 的默认初始容量是多少?HashMap 的容量有什么限制吗?
默认初始容量是16。HashMap 的容量必须是2的N次方,
9.你说 HashMap 的容量必须是 2 的 N 次方,这是为什么?
计算索引位置的公式为:(n - 1) & hash,当 n 为 2 的 N 次方时,n - 1 为低位全是 1 ,此时任何值跟 n - 1 进行 & 运算的结果为该值的低 N 位,达到了和取模同样的效果,实现了均匀分布。实际上,这个设计就是基于公式:x mod 2^n = x & (2^n - 1),因为 & 运算比 mod 具有更高的效率。
当 n 不为 2 的 N 次方时,hash 冲突的概率明显增大。
10.HashMap 的插入流程是怎么样的?
11.扩容(resize)流程介绍下?
12. JDK 1.8 主要进行了哪些优化?
1)底层数据结构从“数组+链表”改成“数组+链表+红黑树”,主要是优化了 hash 冲突较严重时,链表过长的查找性能:O(n) -> O(logn)。
2)计算 table 初始容量的方式发生了改变,老的方式是从1开始不断向左进行移位运算,直到找到大于等于入参容量的值;新的方式则是通过“5个移位 + 或等于运算”来计算。
3)优化了 hash 值的计算方式,新的只是简单的让高16位参与了运算。
4)扩容时插入方式从“头插法”改成“尾插法”,避免了并发下的死循环。
5)扩容时计算节点在新表的索引位置方式从“h & (length-1)”改成“hash & oldCap”
13.除了 HashMap,还用过哪些 Map,在使用时怎么选择?
11.volatile关键字
1.volatile关键字是由JVM提供的最轻量级同步机制
volatile关键字的特性
1.被volatile修饰的变量保证对所有线程可见。
volatile修饰的变量并不保值原子性
AtomicInteger实现了原子性,作为对照组
2.禁止指令重排序优化。