B站面试
Java基础
ArrayList与LinkedList的区别?
- ArrayList是基于动态数组实现,LinedList是基于链表结构实现的
- 对于随机的访问ArrayList要优先于Linkedist,linkedList需要移动指针
- 对于插入、删除数据LinkedList只需要改变前后的指针引用即可,ArrayList需要移动插入/删除的元素 比较耗时
AQS简述其中一些常用的技术(ReentrantLock)
AQS(AbstracyQueueSynchronizer)抽象队列同步器
state(int) Queue(null)主要实现的原理涉及的2个参数
引申:Synchronized与ReentrantLock的区别?
- Synchronized锁的转换,偏向锁(无锁->偏向锁->轻量级锁->重量级锁)?
Synchronized是Java关键字,是JVM层面的锁,在代码块/方法中通过notify/wait来实现,ReentrantLock是jdk原生的锁,底层原理是CAS
- 是否可以手动释放:Synchronized不可以手动释放锁,必须代码块/方法执行完成后才可以释放锁,ReentrantLock如果忘记了释放锁就会一直持有锁导致死锁,所以一般将释放锁的操作放在finally中执行释放unlock()操作。
- 是否可以中断:Synchronized是不可中断的锁,必须是线程执行完成/代码中出现异常。ReentrantLock是可以中断的,tryLock(timeout,TimeUnit)设置超时方法或者将lockInterruptibly()放到代码块中,调用interrupt方法进行中断
- 是否公平:synchronized为非公平锁 ReentrantLock则即可以选公平锁也可以选非公平锁,通过构造方法new ReentrantLock时传入boolean值进行选择,为空默认false非公平锁,true为公平锁
- 锁的唤醒的精确:synchronized不能绑定; ReentrantLock通过绑定Condition结合await()/singal()方法实现线程的精确唤醒,而不是像synchronized通过Object类的wait()/notify()/notifyAll()方法要么随机唤醒一个线程要么唤醒全部线程。
- 锁的对象:synchronzied锁的是对象,锁是保存在对象头里面的,根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock锁的是线程,根据进入的线程和int类型的state标识锁的获得/争抢
多线程的使用方式?(线程池使用及一些核心参数简介及线程数如何设置)
多线程一般使用ThreadPoolExecutor来创建自定义线程池,线程池参数
- coreSize:核心线程数
- maxSize:最大线程数
- time:核心线程存活的最长时间
- timeUnit:存活的时间单位
- queue:存储新来的任务的队列
- abortPolicy:拒绝策略
- factory:创建线程的自定义工厂
线程数的创建一般是根据业务的性质决定线程数的配置,一般是CPU密集型/IO密集型
一般是根据这个公式进行计算:线程数=[(线程等待时间+线程处理时间)/线程处理时间]Ncpu数,CPU密集型线程的IO耗时基本为0,也就是计算的结果就是Ncpu数,IO密集型线程等待阻塞时间是线程处理时间的倍数,假设等待时间与处理时间相等那个就是2Ncpu数
Spring中常用的设计模式简介?(代理模式、工厂模式、单例模式)
- 代理模式:AOP切面(JDK动态代理模式&CGlib代理模式)
- 工厂模式:FactoryBean(定义一个创建工厂的接口,对象的创建实例化交由工厂来实现)
- 单例模式:保证实例化的bean只有一个,可以通过singleton=“true|false” 或者 scope=“?”来指定
- 观察者模式:一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,spring中Observer模式常用的地方是listener的实现。如ApplicationListener
- 策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化
volatile和final关键字
- volatile
final(可以修饰类、方法、变量)
- final修饰类:那么这个类是不能被继承的,这个类中的所有的方法隐式的会变成final
- final修饰方法:那么该方法不能被子类重写,需要注意的一点是:因为重写的前提是子类可以从父类中继承此方法,如果父类中final修饰的方法同时访问控制权限为private,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与final的矛盾,而是在子类中重新定义了新的方法。(注:类的private方法会隐式地被指定为final方法。
final修饰变量:只能被赋值一次,赋值后不再改变,当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;
- 如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。
- final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。
- 当函数的参数类型声明为final时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值
ThreadLocal的使用,以及使用的注意点?为什么会内存泄漏?
- ThreadLocal是多线程情况下防止对同一变量进行操作,每个线程有自己的本地变量进行操作,不互相影响.
- ThreadLocal的存储方式类似Map结构key和value,存储set,取值get,删除remove,key为当前线程的引用
- ThreadLocal的key是软引用,因为ThreadLcoal的存储结构是Entry,而Entry继承了WeakReference
>,当存储数据的时候new出来的实例Entry,存储的key就是弱引用,这样会导致一个内存溢出的问题,当内存不足的时候JVM会进行GC,GC会将软引用进行回收,那么key被回收了,value会一直存在,这会使得内存不断的扩大但又无法进行回收。所以 在使用ThreadLocal的时候使用完成后一定要在finally块中进行remove
Java中对象的引用分为哪几类?
- 强引用(StrongReference)
最强悍的一般都是new出来的实例对象 - 软引用(SoftReference)
它的作用是告诉垃圾回收器,程序中的哪些对象是不那么重要,当内存不足的时候是可以被暂时回收的。当JVM中的内存不足的时候,垃圾回收器会释放那些只被软引用所指向的对象。如果全部释放完这些对象之后,内存还不足,才会抛出OutOfMemory错误。软引用非常适合于创建缓存。当系统内存不足的时候,缓存中的内容是可以被释放的 - 弱引用(WeakReferene)
它的作用是引用一个对象,但是并不阻止该对象被回收。如果使用一个强引用的话,只要该引用存在,那么被引用的对象是不能被回收的。弱引用则没有这个问题。在垃圾回收器运行的时候,如果一个对象的所有引用都是弱引用的话,该对象会被回收。弱引用的作用在于解决强引用所带来的对象之间在存活时间上的耦合关系。弱引用最常见的用处是在集合类中,尤其在哈希表中。哈希表的接口允许使用任何Java对象作为键来使用。当一个键值对被放入到哈希表中之后,哈希表对象本身就有了对这些键和值对象的引用。如果这种引用是强引用的话,那么只要哈希表对象本身还存活,其中所包含的键和值对象是不会被回收的。如果某个存活时间很长的哈希表中包含的键值对很多,最终就有可能消耗掉JVM中全部的内存 - 虚引用(PhantomReference)
在Object类里面有个finalize方法,其设计的初衷是在一个对象被真正回收之前,可以用来执行一些清理的工作。因为Java并没有提供类似C++的析构函数一样的机制,就通过 finalize方法来实现。但是问题在于垃圾回收器的运行时间是不固定的,所以这些清理工作的实际运行时间也是不能预知的。幽灵引用(phantom reference)可以解决这个问题。在创建幽灵引用PhantomReference的时候必须要指定一个引用队列。当一个对象的finalize方法已经被调用了之后,这个对象的幽灵引用会被加入到队列中。通过检查该队列里面的内容就知道一个对象是不是已经准备要被回收了.
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动.
- 强引用(StrongReference)
- ConcurrentHashMap与Map的区别以及ConcurrentHashMap的实现原理?
- 多路复用IO的原理?
中间件
redis的基本数据结构?
- String、Set、Sorted Set、Map、List
redis基本数据类型结合具体的场景?
- 实现购物车用什么数据结构类型? -- Map
- 实现随机取出3个获奖名单?-- set,使用set的pop弹出命令
- 实现排名的前3名?-- Sorted Set的ZRang命令排序取前3个数值
- 取一个人的共同好友? -- Set,使用set的union命令
redis为什么很快?
- 单线程,无需线程的上下文切换
- 内存操作
- 多路复用IO模型
redis实现分布式锁?
- redis实现分布式锁方式是setnx命令,也就是多线程在并发的时候对同一个key进行设置数值,其他的线程在进行抢占锁的时候会发现这个key已经存在数值,那么就会放弃这个抢占锁的操作
redis的分布式锁的实现会遇到很多坑:
- A线程在setnx之后一直处理业务不释放锁,就会导致其他的线程一直等待 无法拿到锁,这样线程池就会被打满,所以线程在设置锁时候需要添加锁的超时时间,当锁的执行超过了时间就会主动释放锁
A线程在执行过程中在规定的超时时间内完成业务处理并发送了释放了锁的del命令,但是由于系统负荷/网络原因导致命令没有发送成功,到了超时时间,主动释放了锁,那么B线程拿到了锁,B线程在处理过程中进行,此时A的命令重试成功删除了锁,那么B这个会有不可预计的错误.所以我们在del的时候要先拿到这个key对应的value,value的值不要唯一这样会出现误删除锁的情况,并且get和del的操作要保证原子性(Lua脚本来编写,lua脚本只是一行操作命令开始和结束中间无论多少操作,都是一次性执行)
- zookeeper与eureka的区别?使用注册中心的优缺点?
- 两个都是先实现服务注册的中间件
- CAP理论:C:一致性 A:可用性 P:分区容错性
zookeeper只能保证CAP理论中的CP,一致性其实并不一定是强一致性而是最终一致性,P分区容错型也就是就集群的高可用,这个是分布式系统的基础,由于zk的选举机制会导致zk的集群的不可用,当zk的主挂了之后进行选举的时候 整个集群是不可用的这个在分布式系统是致命的,可以容忍数据的不一致但是不可用这个无论那个系统业务都无法承受的(当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪)。
- eureka保证了CAP中的AP:P就不说了这是基础,C也不是强一致性保证最终一致性即可,A:可用性,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障:1.Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务2.Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)3.当网络稳定时,当前实例新的注册信息会被同步到其它节点中(Eureka拥有客户端缓存技术:Eureka分为客户端程序与服务器端程序两个部分,客户端程序负责向外提供注册与发现服务接口)。 所以即便Eureka集群中所有节点都失效,或者发生网络分割故障导致客户端不能访问任何一台Eureka服务器;Eureka服务的消费者仍然可以通过 Eureka客户端缓存来获取现有的服务注册信息。甚至最极端的环境下,所有正常的Eureka节点都不对请求产生相应,也没有更好的服务器解决方案来解 决这种问题时;得益于Eureka的客户端缓存技术,消费者服务仍然可以通过Eureka客户端查询与获取注册服务信息)
- zookeeper来实现分布式锁?
- 首先zk有4个节点类型:持久节点、持久顺序节点、临时节点、临时顺序节点
zk实现分布式锁的节点就是利用了临时顺序节点的特性,实现zk的分布式锁一个持久节点作为root节点,在该节点下新建临时顺序节点,比如Client-A在/root/lock1节点,CLient-B在目录下新建/root/lock2节点,所有的节点都是顺序递增的,那么客户端在获取锁的时候判断自己的节点是否是当前节点集合中的最小节点,是就获取到锁,不是就在当前节点的上个节点添加watcher来监听lock1节点是否存在,CLient-C则监听lock2,记得不是监听第一个节点,如果监听第一个节点,当节点删除后,所有的临时节点都会收到这个消息,多个节点又同时去争抢这个锁(羊群效用),当变化发生时,ZooKeeper会触发一个特定的znode节点的变化导致的所有监视点的集合。如果有1000个客户端通过exists操作监视这个znode节点,那么当znode节点创建后就会发送1000个通知,因而被监视的znode节点的一个变化会产生一个尖峰的通知,该尖峰可能带来影响,例如,在尖峰时刻提交的操作延迟。可能的话,我们建议在使用ZooKeeper时,避免在一个特定节点设置大量的监视点,最好是每次在特定的znode节点上,只有少量的客户端设置监视点,理想情况下最多只设置一个.
- 消息的重复消费如何处理?消息来实现分布式事务?
数据库
Mysql使用的引擎是什么?有什么区别?
- InnerDB/MySlamDB
- MySlam:不支持事务也不支持外键,不支持行级锁,只支持并发插入的表锁,MyISAM也是使用B+tree索引但是和Innodb的在具体实现上有些不同(优缺点:MyISAM的优势在于占用空间小,处理速度快。缺点是不支持事务的完整性和并发性)
- InnerDB支持事务,提供自增长列,支持外键指出MVCC行级锁支持的索引类型是B+树(InnoDB的优势在于提供了良好的事务处理、崩溃修复能力和并发控制。缺点是读写效率较差,占用的数据空间相对较大)
Mysql中的索引的数据结构是什么?B+树与Hash索引的区别?
- InnerDB使用的是B+数结构
Mysql索引失效以及最左匹配原则?
- mysql查询优化器会判断纠正这条sql语句该以什么样的顺序执行效率最高,最后才生成真正的执行计划。所以,当然是我们能尽量的利用到索引时的查询顺序效率最高咯,所以mysql查询优化器会最终以这种顺序进行查询执行。
- explain select * from test where b<10 and c <10;
explain select * from test where a<10 and c <10;
为什么 b<10 and c <10,没有用到索引?而 a<10 and c <10用到了?
当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(a,b,c)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(b,c)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(a,c)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性.
引申想到 ACID的实现原理?数据库的隔离级别?数据库的乐观锁?
ACID:
- A(原子性):一个事务中多个操作要么都成功要么都失败
- C(一致性):在一个事务执行的前后,必须保证从一个一致状态变成另一个一致状态,举个例子:A和B两者的钱一共400元,A和B来回转账,不论转几次,怎么转最终结果都是A和B总计400元
- I(隔离性):一个事务的操作与其他事务的操作是互不影响相互隔离的
- D(持久性):对一个事务进行提交,如果提交成功,那么数据一定永久保存下来,即便系统故障了,恢复以后数据应该仍在。
隔离级别
- 读未提交:啥玩意不是
- 读已提交:可避免脏读
- 可重复读:可避免脏读,不可重复读,这是Mysql默认的隔离级别
- 序列化:可避免脏读,不可重复读,幻读
- oracle只有读已提交(默认)和串型化2种隔离级别
- 脏读:指当一个事务正在访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。
- 不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。
- 幻象读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中
数据结构
- 红黑数与平衡二叉树的区别?
- 红黑树的最长路径与最短路径的关系?
- 引申--Map的数据结构的变动由之前的链表结构-->红黑树的转换?