常见知识点总结:
一、Java基础
1.1、HashMap相关
- 基本数据结构(1.7和1.8)
数组+链表/数组+链表+红黑树,转换时机
- 红黑树的定义(为什么不能是其他树)
基本定义:是一种只含有红黑结点并能自平衡的二叉查找树(左旋、右旋、变色),再插入和删除破坏树的平衡结构时,会进行自旋到平衡
基本性质:
- 性质1:每个节点要么是黑色,要么是红色。
- 性质2:根节点是黑色。
- 性质3:每个叶子节点(NIL)是黑色。
- 性质4:每个红色结点的两个子结点一定都是黑色。
- 性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。
- 性质5.1:如果一个结点存在黑子结点,那么该结点肯定有两个子结点
- 扩容机制(2倍,为什么)
无参构造初始大小16,加载因子0.75,2的幂次,超过长度*负载因子时会2倍扩容,好处:计算索引位置时,
避免了一些Hash冲突,如果当前位置有元素(替换或生成链表,取决于key的值),当链表长度大于8
- hash碰撞定义如何解决 不同的值有不同的Hashcode
可参考文章 - 引申(同类,并发,实现区别,包括ConcurrentHashMap,HashTable)
多线程,扩容的时候线程不安全 HashTable,全部synchronized,
1.8之前用了可重入锁,
1.8之后用了无锁,解决的ConcurrentHashMap ConcurrentHashMap
在JDK1.7版本中,ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成,
Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是上面的提到的锁分离技术,
而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样
JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,
并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,
虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性 - put 方法执行机制
先是
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
再是
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
1.2、内存模型
- 基本内存模型和数据共享区(和JDK版本有关系)
1.8之前
1.8之后
- 关于类加载过程
- 堆内存细分?
1.3、GC
- 基本算法,可参考链接
1、吞吐量:即单位时间内的处理能力。 2、最大暂停时间:因执行GC而暂停执行程序所需的时间。
3、堆的使用效率:鱼与熊掌不可兼得,堆使用效率和吞吐量、最大暂停时间是不可能同时满足的。即可用的堆越大,GC运行越快;相反,想要利用有限的堆,GC花费的时间就越长。
4、访问的局部性:在存储器的层级构造中,我们知道越是高速存取的存储器容量会越小(具体可以参看我写的存储器那篇文章)。由于程序的局部性原理,将经常用到的数据放在堆中较近的位置,可以提高程序的运行效率。
基本算法包括:标记清除算法,复制算法,标记压缩算法
1.4、线程池
1.4.1、基本参数及其含义
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
一、corePoolSize 线程池核心线程大小
线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会 被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。
二、maximumPoolSize 线程池最大线程数量
一个任务被提交到线程池后,首先会缓存到工作队列(后面会介绍)中,如果工作队列满了,则会创建一个新线程,然后从工作队列中的取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize来指定。
三、keepAliveTime 空闲线程存活时间
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
四、unit 空间线程存活时间单位
keepAliveTime的计量单位
五、workQueue 工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:
①ArrayBlockingQueue:基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。
当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,
则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
②LinkedBlockingQuene: 基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。
由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,
而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
③SynchronousQuene:一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,
而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
④PriorityBlockingQueue: 具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
六、threadFactory 线程工厂
创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
七、handler 拒绝策略
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。
这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:
①CallerRunsPolicy:该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务
②AbortPolicy:该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
③DiscardPolicy:该策略下,直接丢弃任务,什么都不做。
④DiscardOldestPolicy:该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
1.4.2、实际操作应注意的
1.核心线程满了,接下来进队列,队列也满了,创建新线程,直到达到最大线程数,之后再超出,会进入拒绝rejectedExecution
1.4.3、JUC下基本线程池
五种基本线程池使用场景
1.4.4、Spring线程池使用
1.5、并发,锁
- synchronized重量级锁
二、Spring/Springboot,基础框架系列知识点
2.1、Bean 生命周期
2.2、SpringBean执行机制
2.3、JPA+QueryDsl相关
2.4、Mybatis相关(1,2级缓存)
本地缓存和二级缓存,范围不同,本地缓存是sqlSession级别,二级缓存基于nameSpace,多表的时候,出现脏数据
三、微服务相关知识点
3.1、微服务基本架构方式
3.1.1、 AlibabaCloud(nacos+sentinel) / Dubbo
3.1.2、 SpringCloud(eureka+feign+hystrix+apollo+zuul+gateway+ribbon)
- 相关组件源码
- 相关组件使用及场景
- 链路追踪
eureka 的相关注册发现源码
feign源码实现
hystrix断路器配置
ribbon负载均衡策略
几种均衡策略包括
- RandomRule 随机策略 随机选择server
- RoundRobinRule 轮询策略 按照顺序选择server(ribbon默认策略)
- RetryRule 重试策略 在一个配置时间段内,当选择server不成功,则一直尝试选择一个可用的server
- BestAvailableRule 最低并发策略 逐个考察server,如果server断路器打开,则忽略,再选择其中并发链接最低的server
- AvailabilityFilteringRule 可用过滤策略 过滤掉一直失败并被标记为circuit tripped的server,过滤掉那些高并发链接的server(active connections超过配置的阈值)
- ResponseTimeWeightedRule 响应时间加权重策略 根据server的响应时间分配权重,响应时间越长,权重越低,被选择到的概率也就越低。响应时间越短,权重越高,被选中的概率越高,这个策略很贴切,综合了各种因素,比如:网络,磁盘,io等,都直接影响响应时间
- ZoneAvoidanceRule 区域权重策略 综合判断server所在区域的性能,和server的可用性,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server
apollo架构和使用
四、MQ
四大MQ,包括Kafka,ActiveMQ,RabbitMQ,RocketMQ
4.1、Kafka
4.1、ActiveMQ
4.1、RabbitMQ
4.1、RocketMQ
五、搜索引擎/Nosql
包括ES,Solr,Redis,MongoDB
5.1、Redis相关
5.1.1、 基本使用场景
- 分布式锁实现、优缺点,和注意事项
- 缓存
- MQ
- 排行榜
六、数据库
6.1、基础知识
- 索引类型和方法
- SQL优化(执行计划+索引+SQL优化)
七、数据结构和算法
- 树