1 获取一个商铺最近一周每一天的销售总量
select date(createtm) createTime, count(*) from cinema where date(createtm) >= date_sub(curdate(), interval 7 day) group by createTime
2 如何判断是否命中索引,优化索引
通过执行计划判断:type, keys, rows,
3 线程有哪些状态,其中的阻塞状态有什么区别?
4 栈中最小元素
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int a) {
stack1.push(a);
if(stack2.isEmpty() || a <= stack2.peek()) {
stack2.push(a);
}
}
public void pop() {
if(stack1.isEmpty()) {
return;
}
int b = stack1.pop();
if(b == stack2.peek()) {
stack2.pop();
}
}
public int min() {
return stack2.peek();
}
5 寻找二叉树每层的最大节点。
public void cengxu(BinaryNode node) {
Queue<BinaryNode> queue = new LinkedList<>();
List<Integer> list = new ArrayList<>();
int size = 1;
int max = 0;
queue.add(node);
while(!queue.isEmpty()) {
BinaryNode node1 = queue.poll();
max = Math.max(max, node1.value);
size--;
System.out.println(node1.value);
if(node1.left != null) {
queue.add(node1.left);
}
if(node1.right != null) {
queue.add(node1.right);
}
if(size == 0) {
list.add(max);
max = 0;
size = queue.size();
}
}
}
5 缓存与数据库一致性问题
延时双删策略。
删除失败的重试策略。
6 数据库的左连接、右连接、内连接、外连接。
left join (左连接):返回包括左表中的所有记录和右表中连接字段相等的记录。 right join (右连接):返回包括右表中的所有记录和左表中连接字段相等的记录。 inner join (等值连接或者叫内连接):只返回两个表中连接字段相等的行。 full join (全外连接):返回左右表中所有的记录和左右表中连接字段相等的记录。
select a.name,b.job from A a left join B b on a.id=b.A_id
select a.name,b.job from A a right join B b on a.id=b.A_id
7 分库分表
网关
以订单号为shardingkey
猫眼日订单100w
年订单4亿。
那么分为64张表。
单表年增长量600w.
交易:
以userId为shardingkey
4*128=512张表
单表年增长量为80w。
分布式id生成算法:雪花算法。
其中嵌入userId即可满足根据userId和orderId查询,根据userId做hash。
8 考试分数记录表,id,strudent,subject,score,选出每个student的最高score记录,包含所有字段
select * from (select ctId, max(id) maxId from cinema group by ctId) t, cinema b where t.maxId = b.id
9 判断是否需要被回收
引用计数法和可达性分析法
CMS带来的问题:
会消耗CPU的资源;
Concurrent Mode Failure问题;
一般会设置一个参数“-XX:CMSInitiatingOccupancyFaction”,老年代占用多少比例时,就是进行触发CMS。如果在这个期间,用户进程分配不到空间,则会触发这个。
此时会退化到serial Old替代CMS,就让人很难受了。
内存碎片问题。
为什么fullGC很慢?
新生代因为存活的对象很少,复制的很快。
但是CMS分为4个过程。
10 sychronized1.8后做了哪些优化
锁可以降级,不过会影响性能,只有在STW的时候,JVM进入到安全点,发现有闲置的monitor(重量级锁对象),会进行锁降级。
去除偏向锁,因为涉及到锁的撤销啥的,涉及到需要从用户态切换到内核态。
锁消除:经过逃役分析,发现没必要加锁,就会撤销锁。
11类加载器
类初始化的场景:
new一个对象的时候;
反射获取类的时候;
调用类的静态变量,静态方法时;
初始化一个子类时,先初始化父类;
JVM标明启动类时,文件名和类相同的类。
12 volatile
可见性:
通过内存屏障实现,将当前处理器的缓存行的数据直接写回内存中;将其他CPU缓存了该数据的内存地址置为无效(通过loc k指令)。
为了保证各个处理器的缓存是一致的,实现了缓存一致性协议(MESI)。
有序性:
Happens-before:
对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读。
13 Java线程池的参数
核心线程数
最大线程数
存活时间
单位
存放执行任务的队列 拒绝策略
线程工厂:用来创建线程的工厂,可以自定义线程名称,当进行虚拟机栈分析时,看着名字就知道这个线程是哪里来的,不会懵逼。
14 线程数
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列 LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列 PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列 DelayQueue:一个使用优先级队列实现的无界阻塞队列 SynchronousQueue:一个不存储元素的阻塞队列 LinkedTransferQueue:一个由链表结构组成的无界阻塞队列 LinkedBlockingDueue:一个 由链表结构组成的双向阻塞队列
15 spring的scope
单例
每次获取都会返回一个新的
每次request都会拿到一个新的
每次request的同一个session共用一个
16 spring中常用的设计模式
单例模式:
spring的依赖注入(包括懒加载)都是发生在AbstractBeanFactory中。调用getSingleton获取。使用了双重判断加锁的模式。
简单工厂模式:
传入一个参数,决定返回什么对象。
实现原理:
bean容器的启动阶段:
1 将bean的xml文件转换为beanDefinition
2 有一个beanDefinitionRegistry将他们注册到内部的一个concurrentMap中去。
3 之后,提供了一个接口BeanFactoryPostProcessor可以用来实现我们自己的代码
容器中bean的实例化阶段:
通过反射或者CGLB对bean进行实例化,这个过程有很多扩展点
- 各种的Aware接口 ,比如 BeanFactoryAware,对于实现了这些Aware接口的bean,在实例化bean时Spring会帮我们注入对应的BeanFactory的实例。
- BeanPostProcessor接口 ,实现了BeanPostProcessor接口的bean,在实例化bean时Spring会帮我们调用接口中的方法。
- InitializingBean接口 ,实现了InitializingBean接口的bean,在实例化bean时Spring会帮我们调用接口中的方法。
- DisposableBean接口 ,实现了BeanPostProcessor接口的bean,在该bean死亡时Spring会帮我们调用接口中的方法。
目的:
松耦合:通过beanFactory对依赖与被依赖的bean进行松耦合。
bean的额外处理。
工厂方法模式:
实现原理:
实现了factoryBean的bean是一类叫做factory的bean。
适配器模式
SpringMVC中的适配器HandlerAdatper。
代理模式
AOP的的底层用的就是动态代理:在内存中构建的,不需要手动编写代理类。
实现原理:
切面在应用运行的时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象创建动态的创建一个代理对象。SpringAOP就是以这种方式织入切面的。
织入:把切面应用到目标对象并创建新的代理对象的过程。
17 介绍一下动态代理
以蛋糕的为例。
如果用户想给水果蛋糕上加个东西,需要去修改水果蛋糕机的代码。
违背了对修改关闭,对扩展开放的原则。
怎么解决?
其实只需要作出来后,人工去加点东西即可。那么这个人就是那个代理类。
//水果蛋糕机代理
public class FruitCakeMachineProxy implements CakeMachine{
private CakeMachine cakeMachine;
public FruitCakeMachineProxy(CakeMachine cakeMachine) {
this.cakeMachine = cakeMachine;
}
public void makeCake() {
cakeMachine.makeCake();
System.out.println("adding apricot...");
}
}
//蛋糕店
public class CakeShop {
public static void main(String[] args) {
FruitCakeMachine fruitCakeMachine = new FruitCakeMachine();
FruitCakeMachineProxy fruitCakeMachineProxy = new FruitCakeMachineProxy(fruitCakeMachine);
fruitCakeMachineProxy.makeCake(); //making a Fruit Cake... adding apricot...
}
}
那么现在就又有个问题,如果用户想给水果、巧克力、抹茶蛋糕机上都撒杏仁呢?这样就不会有无数个代理类?
动态代理可以解决这个问题!!!
静态代理可以:针对某种特定的类型(某种蛋糕机)做某种代理动作(撒杏仁)。
动态代理可以:对所有类型做某种代理动作。
//杏仁动态代理
public class ApricotHandler implements InvocationHandler{
private Object object;
public ApricotHandler(Object object) {
this.object = object;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(object, args); //调用真正的蛋糕机做蛋糕
System.out.println("adding apricot...");
return result;
}
}
//撒杏仁的代理写完之后,我们直接让蛋糕店开工:
public class CakeShop {
public static void main(String[] args) {
//水果蛋糕撒一层杏仁
CakeMachine fruitCakeMachine = new FruitCakeMachine();
ApricotHandler fruitCakeApricotHandler = new ApricotHandler(fruitCakeMachine);
CakeMachine fruitCakeProxy = (CakeMachine)Proxy.newProxyInstance(fruitCakeMachine.getClass().getClassLoader(),
fruitCakeMachine.getClass().getInterfaces(), fruitCakeApricotHandler);
fruitCakeProxy.makeCake();
//巧克力蛋糕撒一层杏仁
CakeMachine chocolateCakeMachine = new ChocolateCakeMachine();
ApricotHandler chocolateCakeApricotHandler = new ApricotHandler(chocolateCakeMachine);
CakeMachine chocolateCakeProxy = (CakeMachine) Proxy.newProxyInstance(chocolateCakeMachine.getClass().getClassLoader(),
chocolateCakeMachine.getClass().getInterfaces(), chocolateCakeApricotHandler);
chocolateCakeProxy.makeCake();
}
}
与静态代理相比,动态代理具有更加的普适性,能减少更多重复的代码。
试想这个场景如果使用静态代理的话,我们需要对每一种类型的蛋糕机都写一个代理类(FruitCakeMachineProxy、ChocolateCakeMachineProxy、MatchaCakeMachineProxy等)。
但是如果使用动态代理的话,我们只需要写一个通用的撒杏仁代理类(ApricotHandler)就可以直接完成所有操作了。
直接省去了写 FruitCakeMachineProxy、ChocolateCakeMachineProxy、MatchaCakeMachineProxy 的功夫,极大地提高了效率。
如何使用动态代理?
1 创建一个类,实现InvocationHandler。
2 将该类动态代理了。
3 再调用Proxy.newInstance()生成代理类实例对象。
4 调用代理类的相关方法。
Java动态代理(代理类必须实现目标接口)和CGLB。
通常代理类和委托类会实现相同的接口,所以在访问者看起来,没有任何区别。
动态代理类是这样的:
1 他是在运行时生成的class,在他生成时必须提供一组interfaces给它,然后class对外宣称自己实现了这些接口。因此,我们可以把该class的实例当作这些interface中的任何一个来用。这个DynamicProxy代理类其实就是一个代理,不会做任何工作,生成时也必须提供一个handler给它,由它接管实际的工作。
2 当我们通过代理对象来调用方法的时候,其实是委托到关联的handler的invoke方法来执行的,是通过代理的方式来做的,这就是动态代理。通过这种方式,被代理的对象可以在运行时改变。
为什么会自动调用handler的invoke方法?
答:因为最终生成的代理类,它继承自Proxy并且实现了Subject接口,在接口内部,通过反射调用了invoke方法。
那么,是如何实现的呢?
18 事务的ACID
原子性,一致性,隔离性,持久性。
MySQL中服务器层不管理事务,事务是由存储引擎实现的。
Innodb默认的隔离级别是可重复读,不满足隔离性。
19 redis的持久化
RDB和AOF
RDB(将数据以二进制的方式保存在磁盘中):定时做个快照,save 100 2
问题:
是否会停止对外服务?
不会
如果不停止,那么怎么处理新的请求?
主进程会fork出一个字进程来处理,首先是共享所有的数据内存,如果此时主进程进行数据更改,那么会复制一份该数据的内存页,进行修改,最后进行替换即可。
优点:数据格式,节省空间。fork子进程来,性能最好。
缺点:容易丢失数据;持久化期间,如果大量写进来,采用写时复制,会导致内存暴增。
AOF(以命令的方式保存在磁盘中):日志追加
有一个缓冲区,采用配置,每次/每秒/操作系统决定
涉及到日志重写,fork子进程,不是对老文件处理,从此时的内存中进行重写日志
优点:如果配置每次写回,不会丢失数据。
缺点:数据集大的时候,比RDB启动效率低。
20 微服务
定义:将单一应用程序划分为一组小的服务,服务之间相互配合,为用户提供最终的价值。
应当避免集中式的服务管理机制。
21 mysql解决幻读
对于当前读:采用行锁+间隙锁解决 对于唯一索引的等值查询,只需要行锁。非等值查询和普通所有的查询,采用行锁+间隙锁。 对于快照读:采用MVCC undolog链+readview,首先判断该条记录上的事误id,如果小于活跃事务中的最小id,那么可以看见。如果大于,判断是否在活跃列表中,如果在,则看不见,向前找。如果不在,则可以看见。
22 redis的缓存淘汰策略和淘汰机制
- 惰性删除 好处:对CPU友好。坏处:有些不用的永远也清除不了。
- 定期删除 redis会定时100ms检测一次,随机抽取,若删除的个数超过25个,则重复执行一次。对每次执行的时常也有限制,保证不影响正常的读写。只有主节点执行,然后同步给从节点。
- 内存触发最大容量,主动清除。 都没有限制住的话,就会执行主动清除。
内存淘汰机制 no-eviction
当内存不足以容纳新写入数据时,新写入操作会报错,无法写入新数据,一般不采用。
allkeys-lru
当内存不足以容纳新写入数据时,移除最近最少使用的key,这个是最常用的
allkeys-random
当内存不足以容纳新写入的数据时,随机移除key
allkeys-lfu
当内存不足以容纳新写入数据时,移除最不经常(最少)使用的key
volatile-lru
当内存不足以容纳新写入数据时,在设置了过期时间的key中,移除最近最少使用的key。
volatile-random
内存不足以容纳新写入数据时,在设置了过期时间的key中,随机移除某个key 。
volatile-lfu
当内存不足以容纳新写入数据时,在设置了过期时间的key中,移除最不经常(最少)使用的key
volatile-ttl
当内存不足以容纳新写入数据时,在设置了过期时间的key中,优先移除过期时间最早(剩余存活时间最短)的key。
因为全部搞的话,会很耗费内存,所以他是随机挑选5个,淘汰最旧的一个。
23 线程池的类型
使用线程池好处:便于管理,避免无限制创建。 1 newCachedThreadPool 根据需要创建。复用前一个线程,如果没有可用的,则创建一个线程并加入到池子里去。 无限线程数量。 2 newFixedThreadPool 指定最大线程数,提交一个任务创建一个线程,达到最大,则加入到队列中。 3 newScheduledThreadPool 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。 4 newSingleThreadExecutor 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。 可以保证顺序的执行各任务。
24 B-树和B+树区别
-
单一节点存储的元素更多,使得查询的IO次数更少,所以也就使得它更适合做为数据库MySQL的底层数据结构了。
-
所有的查询都要查找到叶子节点,查询性能是稳定的,而B树,每个节点都可以查找到数据,所以不稳定。
-
所有的叶子节点形成了一个有序链表,更加便于范围查找。