目录
- 1.java集合知道哪些
- 2.ArrayList和LinkedList插入效率对比
- 3.HashMap的底层结构
- 4.HashMap怎么实现线程安全
- 4.介绍下reentrantlock
- 5.Redis分布式锁的实现原理
- 7.知道哪些排序算法
- 8.快排的原理
- 9.Spring的AOP作用和原理
- 10.MySQL的InnoDB索引结构
- 11.网络中TCP和UDP的区别
- 12.JVM的内存模型
- 13.垃圾回收算法
- 14.复制算法原理
- 15.RabbitMQ中死信队列有什么用
- 16.linux中怎么实时的查看文件(线上日志)
1.java集合知道哪些
List:ArrayList,LinkedList,Vector
Set:HashSet,TreeSet
Map:HashMap,HashTable,TreeMap
2.ArrayList和LinkedList插入效率对比
ArrayList:尾部插入、删除性能可以,其它部分插入、删除都会移动数据,因此性能会低
LinkedList:头插入删除性能高
3.HashMap的底层结构
HashMap1.7底层结构:数组+链表
HashMap1.8底层结构:数组+链表/红黑树
4.HashMap怎么实现线程安全
实现HashMap安全有两种方式
1、使用Collections.synchronizedMap(new HashMap<>());
2、使用 ConcurrentHashMap
SynchronizedMap 一次锁住整张表来保证线程安全,所以每次只能有一个线程来访为 map。
ConcurrentHashMap 使用分段锁来保证在多线程下的性能。
JDK1.8 中为什么使用内置锁 synchronized替换 可重入锁 ReentrantLock?
在 JDK1.6 中,对 synchronized 锁的实现引入了大量的优化,并且 synchronized 有多种锁状态,会从无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁一步步转换。
减少内存开销 。假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承 AQS 来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。
4.介绍下reentrantlock
ReentrantLock
是 Java 中另一种实现线程安全的机制,它是通过 Java 提供的 Lock 接口来实现的。ReentrantLock
实现了 Lock 接口,提供了与 synchronized
关键字类似的功能,但它具有更高的灵活性和可扩展性。ReentrantLock
支持可重入锁,即同一个线程可以多次获取同一个锁,而不会被阻塞。此外,ReentrantLock
还支持公平锁和非公平锁,可以通过参数来控制锁的获取方式。
在底层实现上,ReentrantLock
使用了 CAS(Compare and Swap)操作和 AQS(AbstractQueuedSynchronizer)队列来实现锁的获取和释放。CAS 操作是一种无锁算法,可以避免锁的竞争和阻塞,提高了并发性能。AQS 队列是一个基于链表的队列,用于存储等待获取锁的线程。当一个线程试获取锁时,如果锁已经被其他线程占用,那么这个线程就会被加入到 AQS 队列中,等待锁释放。当锁被释放时,AQS 会从队列中取出一个线程,并将锁分配给它。
5.Redis分布式锁的实现原理
分布式锁的条件和刚需
1.独占性
OnlyOne,任何时刻只能有且仅有一个线程持有
2.高可用
若redis集群环境下,不能因为某一个节点挂了而出现获取锁和释放锁失败的情况
3.防死锁
杜绝死锁,必须有超时控制机制或者撤销操作,有个兜底终止跳出方案
4.不乱抢
防止张冠李戴,不能私下unlock别人的锁,只能自己加锁自己释放。
5.重入性
同一个节点的同一个线程如果获得锁之后,它也可以再次获取这个锁。
1.加synchronize,在单机版是ok的,但是分布式不ok(原因:生产环境下都是分布式集群)
2.在分布式下使用setnx,出异常的话,可能无法释放锁,必须要在代码层面finally释放锁
3.部署了微服务jar包的机器挂了,代码层面根本没有走到finally这块,没办法保证解锁,这个key没有被删除,需要加入一个过期时间限定key(设置key+过期时间分开了,必须要合并成一行具备原子性)
4.张冠李戴,删除了别人的锁
5.finally块的判断+del删除操作不是原子性的,用Lua脚本
Redis调用Lua脚本通过eval命令保证代码执行的原子性
Jedis jedis = RedisUtils.getJedis();
String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then " +
"return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
try {
Object result = jedis.eval(script, Collections.singletonList(Key), Collections.singletonList(value));
if ("1".equals(result.toString())) {
System.out.println("------del REDIS_LOCK_KEY success");
}else{
System.out.println("------del REDIS_LOCK_KEY error");
}
} finally {
if(null != jedis) {
jedis.close();
}
6.无法确保redisLock过期时间大于业务执行时间的问题,要使用Redisson的看门狗
7.知道哪些排序算法
插入排序
冒泡排序
选择排序
快速排序
归并排序
希尔排序
堆排序
桶排序
基数排序
计数排序
8.快排的原理
「快速排序 Quick Sort」是一种基于分治思想的排序算法,运行高效,应用广泛。
快速排序的核心操作是「哨兵划分」,其目标是:选择数组中的某个元素作为“基准数”,将所有小于基准数的元素移到其左侧,而大于基准数的元素移到其右侧。具体来说,哨兵划分的流程为:
- 选取数组最左端元素作为基准数,初始化两个指针 i 和 j 分别指向数组的两端;
- 设置一个循环,在每轮中使用 i(j)分别寻找第一个比基准数大(小)的元素,然后交换这两个元素;
- 循环执行步骤 2. ,直到 i 和 j 相遇时停止,最后将基准数交换至两个子数组的分界线;
哨兵划分完成后,原数组被划分成三部分:左子数组、基准数、右子数组,且满足“左子数组任意元素
≤基准数 ≤右子数组任意元素”。因此,我们接下来只需对这两个子数组进行排序。
class Solution {
public int[] sortArray(int[] nums) {
quickSort(nums,0,nums.length-1);
return nums;
}
public int partition(int[] nums,int i ,int j){
int pivot = i;
int left = i;
int right = j;
while(left<right){
while(left<right && nums[right]>=nums[pivot]) right--;
while(left<right && nums[left]<=nums[pivot]) left++;
swap(nums,left,right);
}
swap(nums,pivot,left);
return left;
}
public void quickSort(int[] nums,int i ,int j){
if(i>=j){
return;
}
int mid = partition(nums,i,j);
quickSort(nums,i,mid-1);
quickSort(nums,mid+1,j);
}
public void swap(int[] nums,int i ,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
9.Spring的AOP作用和原理
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。横切关注点是在多个模块或功能中重复出现的代码,如日志记录、事务管理、权限控制等。通过使用AOP,开发者可以将这些横切关注点与核心业务逻辑分离,提高代码的可维护性和可重用性。
Spring的AOP原理是基于动代理实现的。在Spring中,AOP通过代理模式实现,它会在运行时动态地生成代理对象,将切面织入到目标对象的方法调用中。Spring支持两种代理方式:JDK动态代理和CGLIB代理。JDK动态代理是基于接口的代理,它只能代理实现了接口的类;而CGLIB代理是基于继承的代理,它可以代理没有实现接口的类。
Spring的AOP实现依赖于以下几个核心概念:
-
切面(Aspect):切面是一个模块化的横切关注点,它包含了一组通知和一个切点。通知定义了在切点处执行的代码,切点定义了在哪些连接点上应用通知。
-
连接点(Join Point):连接点是在应用程序执行程中能够插入切面的点。例如,方法调用、异常处理和字段访问等都是连接点。
-
通知(Advice):通知是在连接点上执行的代码,它定义了在何时、何地、如何执行切面的功能。Spring支持以下五种类型的通知:
-
前置通知(Before Advice):在连接点之前执行的通知。
-
后置通知(After Advice):在连接点之后执行的通知,无论连接点执行成功或失败都会执行。
-
返回通知(After Returning Advice):在连接点正常返回后执行的通知。
-
异常通知(After Throwing Advice):在连接点抛出异常后执行的通。
-
环绕通知(Around Advice):在连接点前后都执行的通知,可以自由控制连接点的执行。
-
-
切点(Pointcut):切点是一个表达式,它定义了哪些连接点应该被应用通知。Spring支持种切点表达式,例如基于方法名、基于注解、基于类型等。
-
引入(Introduction):引入是一种特殊通知类型,它允许向现有的类添加新的方法和属性。引入通知可以让我们在不修改现有代码的情况下,为现有类添加新的功能。
总之,Spring的AOP通过动态代理实现,它允许开发人员在不改变原有代码的情况下,通过在代码中插入切面来实现横切关注点的功能。
10.MySQL的InnoDB索引结构
InnoDB索引采用了B-Tree的数据结构,数据存储在叶子节点上,每个叶子节点默认的大小是16KB。
为啥要使用B+Tree,而不是用普通二叉树和AVL平衡二叉树呢
- 普通二叉树
1、一个节点只能有两个子节点,也就是一个节点度不能超过2
2、左子节点 小于 本节点;右子节点大于等于 本节点,比我大的向右,比我小的向左
问题
这种方式查找:时间复杂度会再次上升
- AVL平衡二叉树
在插入数据的时候会自动发生n多次旋转操作,旋转操作会消耗一定的性能!旋转的目的就是保证这个棵数的平衡。平衡二叉树要求左子树与左子树的高度差不能超过1
问题
在构建二叉树时,需要多次进行i/o操作(海量数据存在数据库或文件中),节点海量,构建二叉树时,速度有影响。
节点海量,也会造成二叉树的高度很大,会降低操作速度。
红黑树 VS AVL树
红黑树是通过复杂的节点插入、节点颜色变换后实现的:这些功能经典的AVL树也能实现,为什么要提出红黑树?
首先红黑树是不符合AVL树的平衡条件的,即每个节点的左子树和右子树的高度最多差1的二叉查找树。但是提出了为节点增加颜色,红黑是用非严格的平衡来换取增删节点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决,而AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多。所以红黑树的插入效率更高!!!
AVL树和红黑树都是自平衡二叉搜索树,它们的平衡策略和性能表现有所不同。如果需要快速的查找操作,可以选择AVL树;如果需要频繁的插入和删除操作,可以选择红黑树。
- B-Tree树
B-Tree是一种多路平衡查找树它可以用于存储大量的数据,并且可以在对数时间内进行查找、插入和删除操作。B-Tree的结构非常适合在磁盘上存储数据,因为它可以最小化磁盘I/O操作的次数。
黄色:指针,存储子节点的信息地址
红色:键值,表中记录的主键
蓝色:数据,记录表中除主键外的数据
以上图为例:若查询的数值为5:
第一次磁盘IO:根据根节点找到磁盘块1,读入内存,执行二分查找,比17小,根据指针P1,找左子树;
第二次磁盘IO:找到磁盘块2,读入内存,执行二分查找,比8小,根据指针P1,找左子树;
第三次磁盘IO:找到磁盘块5,读入内存,执行二分查找,找到5,终止。
整个过程中,我们可以看出:BTree相对于平衡二叉树降低了树的高度,缩减了节点个数,减少了I/O操作,提高了查询效率
还可以优化!让每个磁盘块存储更多的指针
B+Tree
B+树是B树的升级版本,区别是所有数据只出现在叶子结点中,即叶只有叶子节点存放数据,非叶子节点只是叶子结点中数据的键值和指针。
11.网络中TCP和UDP的区别
连接
TCP 是面向连接的传输层协议,传输数据前先要建立连接。
UDP 是不需要连接,即刻传输数据。
服务对象
TCP 是一对一的两点服务,即一条连接只有两个端点。
UDP 支持一对一、一对多、多对多的交互通信
可靠性
TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按需到达。
UDP 是尽最大努力交付,不保证可靠交付数据。
拥塞控制、流量控制
TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。
首部开销
TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。
UDP 首部只有 8 个字节,并且是固定不变的,开销较小。
传输方式
TCP 是流式传输,没有边界,但保证顺序和可靠。
UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
分片不同
TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层。
TCP 和 UDP 应用场景:
- 由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:
FTP 文件传输;
HTTP / HTTPS; - 由于 UDP 面向无连接,它可以随时发送数据,再加上UDP本身的处理既简单又高效,因此经常用于:
包总量较少的通信,如 DNS 、SNMP 等;视频、音频等多媒体通信;广播通信;
12.JVM的内存模型
计算机存储结构,从本地磁盘到主存到CPU缓存,也就是从硬盘到内存,到CPU。
问题?和推导出我们需要知道JMM
因为有这么多级的缓存(cpu和物理主内存的速度不一致的),
CPU的运行并不是直接操作内存而是先把内存里边的数据读到缓存,而内存的读和写操作的时候就会造成不一致的问题
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并不真实存在它仅仅描述的是一组约定或规范,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式并决定一个线程对共享变量的写入何时以及如何变成对另一个线程可见,关键技术点都是围绕多线程的原子性、可见性和有序性展开的。
原则:
JMM的关键技术点都是围绕多线程的原子性、可见性和有序性
展开的
能干嘛?
1 通过JMM来实现线程和主内存之间的抽象关系。
2 屏蔽各个硬件平台和操作系统的内存访问差异以实现让Java程序在各种平台下都能达到一致的内存访问效果。
可见性
是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道该变更
,JMM规定了所有的变量都存储在主内存中。
原子性
指一个操作是不可中断的,即多线程环境下,操作不能被其他线程干扰(比如synchronize,lock)
有序性
对于一个线程的执行代码而言,我们总是习惯性认为代码的执行总是从上到下,有序执行。但为了提供性能,编译器和处理器通常会对指令序列进行重新排序。指令重排可以保证串行语义一致,但没有义务保证多线程间的语义也一致,即可能产生"脏读",简单说,两行以上不相干的代码在执行的时候有可能先执行的不是第一条,不见得是从上到下顺序执行,执行顺序会被优化。
13.垃圾回收算法
- 标记-清除算法
执行过程:
标记: Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。
清除: Collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收。
缺点
1、效率比较低:递归与全堆对象遍历两次
2、在进行GC的时候,需要停止整个应用程序,导致用户体验差
3、这种方式清理出来的空闲内存是不连续的,产生内存碎片。
- 复制算法
将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。
优点:
没有标记和清除过程,实现简单,运行高效
复制过去以后保证空间的连续性,不会出现“碎片”问题。
缺点:
此算法的缺点也是很明显的,就是需要两倍的内存空间。
对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小。
应用场景
在新生代,对常规应用的垃圾回收,一次通常可以回收70%-99%的内存空间。回收性价比很高。所以现在的商业虚拟机都是用这种收集算法回收新生代。
比如:IBM 公司的专门研究表明,新生代中 80% 的对象都是“朝生夕死”的。
- 标记-压缩算法
执行过程:
第一阶段和标记-清除算法一样,从根节点开始标记所有被引用对象
第二阶段将所有的存活对象压缩到内存的一端,按顺序排放。
之后, 清理边界外所有的空间。
标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此,也可以把它称为标记-清除-压缩(Mark-Sweep-Compact)算法。
**年轻代特点:**区域相对老年代较小,对象生命周期短、存活率低,回收频繁。
这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象大小有关,因此很适用于年轻代的回收。而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到缓解。
老年代特点:区域较大,对象生命周期长、存活率高,回收不及年轻代频繁。
这种情况存在大量存活率高的对象,复制算法明显变得不合适。一般是由标记-清除或者是标记-清除与标记-整理的混合实现。
14.复制算法原理
将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。
优点:
没有标记和清除过程,实现简单,运行高效
复制过去以后保证空间的连续性,不会出现“碎片”问题。
缺点:
此算法的缺点也是很明显的,就是需要两倍的内存空间。
对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小。
15.RabbitMQ中死信队列有什么用
先从概念解释上搞清楚这个定义,死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer将消息投递到broker或者直接到queue里了,consumer从queue取出消息进行消费,但某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信,自然就有了死信队列;
消息成为死信的三种情况:
- 1.队列消息数量到达限制;比如队列最大只能存储10条消息,而发了11条消息,根据先进先出,最先发的消息会进入死信队列。
- 2.消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
- 3.原队列存在消息过期设置,消息到达超时时间未被消费;
16.linux中怎么实时的查看文件(线上日志)
tail notes.log # 默认显示最后 10 行
实时显示文件
tail -f xxx.log