0.自我介绍
1.JAVA基础
1.string 、stringbuffer、 stringbuilder区别
string是长度是不可变的,stringbuffer、 stringbuilder是长度可以变的,
stringbuffer是线程安全的,stringbuilder是线程不安全
2.常用的集合类、特点、底层实现
set、map、list,hashset、treeset、arraylist、linedlist、hashmap、treemap、linkedhashmap
set是无序不可重复;
hashset是无序不可重复,底层是hashmap;
treeset是有序不可重复,底层是二叉树
list是有序可重复的,
arraylist是有序可重复,底层是数组,查询快;
linkedlist是有序重复,底层是链表,增删快
map底层是键值对,是数组加链表,
hashmap是无序的不安全,可以都null,
hashtable是安全的,不可以null;
treemap底层是二叉树,有序,
linkedmap底层是链表,有序;
concurrenthashmap是hashmap的线程安全的实现
3.有没有看过hashmap的源码、长度、填充因子
HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75,
HashMap扩容时是当前容量翻倍即:capacity*2,HashMap的最大容量是2的n次
当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,
根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,
那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。
如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上
4.hashmap的线程安全实现
hashmap不是线程安全的,可以设值为null,hashtable是线程安全的,不可以设值为null,
hashmap的线程安全实现是concurrentHashmap
5.concurrentHashmap的原理
1.7使用Segment+HashEntry分段锁的方式实现,
1.8则抛弃了Segment,改为使用CAS+synchronized+Node实现,同样也加入了红黑树,避免链表过长导致性能的问题。
6.对数组按照大小排序方法
1.数组的冒泡排序 2.数组的插入排序 3.Arrays.sort()
Arrays.sort()用的是什么排序算法?原理是什么?时间复杂度?
Arrays.sort()用的是快速排序算法。
算法的思想: 分治法:选择基准(一般选第数组一个)将数组一分为二,基准前面的比基准小,基准后面的比基准大,
之后分别对这两部分继续之前的操作,已达到整个数组有序的目的。
时间复杂度:O(nlogn)
冒泡排序:
空间复杂度最好情况是:O(n)
空间复杂度最差情况是:O(n^2)
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {6,3,8,1,9,5};
for (int num : arr){
System.out.print(num);
}
for (int i=0;i<arr.length-1;i++){
for (int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] =temp;
}
}
}
System.out.println();
for (int num : arr){
System.out.print(num);
}
}
}
7.collections中的sort方法的内部实现
基于compareTo()方法
8.compareTo返回的是什么类型
compareTo就是比较两个值,如果前者大于后者,返回1,等于返回0,小于返回-1
9.websocket的实现原理,是长连接还是短链接
websocket基于http,基于一次http握手,长连接
10.http 、tcp、socket的关系
IP协议对应于网络层,TCP协议对应于传输层,而HTTP协议对应于应用层,
tcp/ip是基于socket的,socket则是对TCP/IP协议的封装和应用
11.tcp三次握手和四次挥手
第一次握手:客户端发送syn包到服务器,等待服务器确认;
第二次握手:服务器收到syn包,同时自己也发送一个SYN+ack包;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK,完成三次握手。
(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。
(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。
(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。
(4)客户端A发回ACK报文确认,设置服务器定时,并将确认序号设置为收到序号加1。
13.java8的新特性
1.lambda表达式和函数式接口
2.接口的默认方法和静态方法
3.方法引用(class::new)
4.新的date和time时间api
5.stream的api
6.重复注解
14.写一个简单的lambda表达式语句
// Java 8之前:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Before Java8, too much code for too little to do");
}
}).start();
//Java 8方式:
new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();
15.sleep和wait区别
sleep()是线程的方法,调用会暂停此线程指定的时间,不会释放对象锁,到时间自动恢复;
wait()是Object的方法,调用会放弃对象锁,进入等待队列,待调用notify()/notifyAll()
唤醒指定的线程或者所有线程,才会进入锁池,不再次获得对象锁才会进入运行状态
16.notify和notifyAll区别
notify只会通知一个在等待的对象,而notifyAll会通知所有在等待的对象,并且所有对象都会继续运行
17.消息队列传送模式?
1.点对点模式
2.发布/订阅模式
18.cookie和session区别?
cookie保存在客户端,session保存在服务器端,
cookie目的可以跟踪会话,也可以保存用户喜好或者保存用户名密码
session用来跟踪会话、用户验证
19.消息中间件用过哪些?
rabbitMQ:数据一致性、稳定性和可靠性要求很高
kafka更适合IO高吞吐的处理,比如ELK日志收集
20.乐观锁和悲观锁
乐观锁
每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁,但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。一般使用version方式和CAS操作方式。
CAS操作方式:
即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。
乐观锁使用场景
比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
悲观锁
每次获取数据的时候,都会担心数据被修改,所以每次获取数据的时候都会进行加锁,确保在自己使用的过程中数据不会被别人修改,使用完成后进行数据解锁。由于数据进行加锁,期间对该数据进行读写的其他线程都会进行等待。在Java中,synchronized的思想也是悲观锁。
悲观锁使用场景
比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
21.CAS和AQS
CAS即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。
可能会出现ABA问题:
如线程1从内存X中取出A,这时候另一个线程2也从内存X中取出A,并且线程2进行了一些操作将内存X中的值变成了B,然后线程2又将内存X中的数据变成A,这时候线程1进行CAS操作发现内存X中仍然是A,然后线程1操作成功。虽然线程1的CAS操作成功,但是整个过程就是有问题的。比如链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。
所以JAVA中提供了AtomicStampedReference/AtomicMarkableReference来处理会发生ABA问题的场景,主要是在对象中额外再增加一个标记来标识对象是否有过变更。
AQS: 队列同步器是用来构建锁或者其他同步组件的基础框架。AQS的主要作用是为Java中的并发同步组件提供统一的底层支持,例如ReentrantLock
,CountdowLatch
就是基于AQS实现的,用法是通过继承AQS实现其模版方法,然后将子类作为同步组件的内部类。
重入锁(ReentrantLock)
都可重入性基于AQS实现:公平锁、非公平锁
2.JVM内容
1.为什么要用类加载器?类加载是做什么的?怎么加载
类加载器负责加载 Java 类的字节代码到 Java 虚拟机中
双亲委派模式
2.类的生命周期
加载 验证 准备 解析 初始化 使用 卸载
3.和服务器类加载的区别(tomcat)
1.启动类去加载
2.系统类去加载
3.webapp应用类去加载(class)
4.webapp应用类去加载(lib)
5.通用类加载器去加载
4.双亲委派模式?为什么要用这个模式?
启动类加载器、扩展类加载器、应用程序类加载器、自定义加载器。。
双亲委派模式:
类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,
如果父类加载器可以完成类加载任务,就成功返回;
只有父类加载器无法完成此加载任务时,才自己去加载防止内存中出现多份同样的字节码
5.java虚拟机的各个部分也存放什么东西
1)程序计数器:几乎不占内存,用于取下一条指令
2)堆:所有通过new创建的对象的内存都在堆中分配,堆被划分为新生代和老年代。
新生代有进一步划分为Eden和Survivor区,最后Survivor由FromSpace和ToSpace组成。
新建的对象都使用新生代分配内存,Eden空间不足,会把存活对象移植到Survivor中。
3)java虚拟机栈:每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧
包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果
4)本地方法栈:用于支持native方法的执行,存储每个native方法调用的状态
5)方法区:存放要加载的类信息、静态变量、常量、属性和方法信息
6.java什么时候gc、gc什么
1. 执行 system.gc()的时候
2.老年代空间不足,一次Full GC 之后,然后不足 会触发 java.outofmemoryError:java heap space
3.永久代空间不足 永生代或者永久代, java.outofMemory PerGen Space
4. minor之后 survior放不下,放入老年代,老年代也放不下,触发FullGC,
或者新生代有对象放入老年代,老年代放不下,触发FullGC
5.新生代晋升为老年代时候,老年代剩余空间低于新生代晋升为老年代的速率,会触发老年代回收
6. new 一个大对象,新生代放不下,直接到老年代,空间不够,触发FullGC
7.minor gc/full gc的触发条件
1.引用计数法:超出了作用域或引用计数为空的对象;
2.可达性分析:从gc root开始搜索找不到的对象,而且经过一次标记、清理,仍然没有复活的对象
8.java收集策略
标记-清除算法
标记-整理算法
复制算法
分代收集算法
新生代是用复制算法,minor gc
老年代是用标记-整理算法,full gc
9.oom情况?手写一个oom的代码?工作中怎么解决的
1、堆溢出:不断地创建对象,并且这些对象不会被回收,首先要确认内存中的对象是
否是必要的,也就是要区分出现的是内存泄露(Memory Leak)还是内存溢出(Memory Overflow)
如果是内存泄露,要使用工具查看泄露对象到GC Roots的引用链,找到泄露对象是通过怎样
的路径与GC Roots相关联并导致垃圾收集器无法自动回收它们。
如果不是内存泄露,那么就要检查JVM参数(-Xmx与-Xms)
2、 java栈溢出:如果虚拟机在扩展栈时无法申请足够的内存空间,
则抛出OutOfMemoryError异常
解决办法一个是减少线程数量,若不能减少线程数量,那么考虑“减少内存”的手段来解
决,即通过减少最大堆和减少栈容量来换取更多的线程
3、直接内存溢出:DirectMemory容量默认值与java堆最大值(-Xmx)一样大,也可以通过-XX:MaxDirectMemorySize指定
由DirectMemory导致的内存溢出,可以考虑一下是不是由于程序中使用了NIO导致的,进行排查
public class HeapOOM {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while (true) {
list.add(new OOMObject());
}
}
}
看内存工具:jmap、jconsolne
1.先用top命令,找到cpu占用最高的进程 PID
2.再用ps -mp pid -o THREAD,tid,time 查询进程中,那个线程的cpu占用率高 记住TID
3.jstack 29099 >> xxx.log 打印出该进程下线程日志
4.sz xxx.log 将日志文件下载到本地
5.将查找到的 线程占用最高的 tid 上上上图中 29108 转成16进制 --- 71b4
6.打开下载好的 xxx.log 通过 查找方式 找到 对应线程 进行排查
3.多线程
1.实现多线程的方式
1、继承thread类
2、实现runnable接口
3、实现callable接口,带返回值
2.1.java什么叫线程安全?什么叫不安全?
就是线程同步的意思,就是当一个程序对一个线程安全的方法或者语句进行访问的时候,其他的不能再对他进行操作了,必须等到这次访问结束以后才能对这个线程安全的方法进行访问
什么叫线程安全:
所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
2.控制多线程重复数据(线程同步方法)
1、synchronized修饰方法
2、synchronizaed修饰代码块
3、volatile修饰变量
4、重入锁(reetrantlock)
3.synchronized 和 volatile 区别
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
4.线程池方式
ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
- corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
- maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
- keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
- unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
- workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
java线程池中的当前线程数 核心线程数 最大线程数 阻塞队列 拒绝策略 的基本使用
当新提交一个任务时:
(1)如果poolSize<corePoolSize,新增加一个线程处理新的任务。
(2)如果poolSize=corePoolSize,新任务会被放入阻塞队列等待。
(3)如果阻塞队列的容量达到上限,且这时poolSize<maximumPoolSize,新增线程来处理任务。
(4)如果阻塞队列满了,且poolSize=maximumPoolSize,那么线程池已经达到极限,会根据饱和策略RejectedExecutionHandler拒绝新的任务。
常用的线程池:
- public static ExecutorService newSingleThreadExecutor() 创建仅有一个线程工作的线程池,相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么将创建有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
- public static ExecutorService newCachedThreadPool() 创建一个缓存线程池,如果线程池的大小超过了任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又动态添加新线程来处理任务。此线程池没有对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)所能够创建的最大线程大小。
- public static ExecutorService newFixedThreadPool(int nThreads) 创建指定大小的线程池。每次提交一个任务就创建一个线程,直到线程数量达到线程池的最大值。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
- public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 类似于newCachedThreadPool,创建一个缓存线程池,此线程池还支持定时以及周期性执行任务。
- public static ScheduledExecutorService newSingleThreadScheduledExecutor() 类似于newSingleThreadExecutor,创建一个单线程的线程池,此线程池还支持定时以及周期性执行任务。
6.拒绝策略
1.ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
2.ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
3.ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
4.ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
6.ThreadLocal是什么
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题,以空间换时间。
4.高并发
1.秒杀并发控制设计
1.页面设置成静态html
2.限流: 鉴于只有少部分用户能够秒杀成功,所以要限制大部分流量,只允许少部分流量进入服务后端。
3.削峰:对于秒杀系统瞬时会有大量用户涌入,把瞬间的高流量变成一段时间平稳的流量。
实现削峰的常用的方法有利用缓存和消息中间件等技术。
4.异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,
其实异步处理就是削峰的一种实现方式。nio
5.内存缓存:秒杀系统最大的瓶颈一般都是数据库读写,如果能够把部分数据或业务逻辑转移到内存缓存,
效率会有极大地提升。redis
3.redis是单线程吗?为什么这么快?
单线程
1.绝大部分请求是纯粹的内存操作(非常快速)
2.采用单线程,避免了不必要的上下文切换和竞争条件
3.非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。
4.redis数据类型
String
Hash
List
Set
Sorted set(zset)
5.redis和Memcached区别
1 、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
2 、Redis支持数据的备份,即master-slave模式的数据备份。
3 、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
6.redis哨兵模式
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
这里的哨兵有两个作用
-
通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
-
当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
故障切换:假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行故障切换过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行故障切换操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
哨兵的工作方式:
1):每个哨兵以每秒钟一次的频率向它所知的Master,Slave以及其他 哨兵 实例发送一个 PING 命令
2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 哨兵标记为主观下线。
3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 哨兵要以每秒一次的频率确认Master的确进入了主观下线状态。
4):当有足够数量的 哨兵(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线
5):在一般情况下, 每个 哨兵 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令
6):当Master被 哨兵标记为客观下线时,哨兵向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次
7):若没有足够数量的 哨兵同意 Master 已经下线, Master 的客观下线状态就会被移除。
若 Master 重新向 哨兵的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。
7.redis 持久化的两种方式
- RDB:RDB 持久化机制,是对 redis 中的数据执行周期性的持久化。
- AOF:AOF 机制对每条写入命令作为日志,以
append-only
的模式写入一个日志文件中,在 redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。
redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制
5.消息中间件
1..消息中间件?消息中间件特点、应用场景?
- 异步处理,用户注册
- 解耦,用户下单
- 流量削峰,秒杀活动
- 日志处理 (kafka)
- 消息通讯,点对点,聊天室
- 系统可用性降低(MQ挂了、MQ高可用性)
- 系统复杂度提高(重复消费、消息丢失)
- 数据一致性(B、C成功,D失败)
RabbitMq有三种模式:
-
单机模式
-
普通集群模式(无高可用性) 互相拉取,做本地持久化,可以提高吞吐量
-
镜像集群模式(高可用性) 每个MQ都有完整的镜像,太消耗资源
方法1,比如消费者拿数据是要写库的,先根据主键查一下,有就不插入或者update,否则就插入
方法2,比如消费者是要写redis,反正每次都是set,天然幂等性
方法3,生产者发送每条数据时,加一个全局的唯一id,类似订单id,到消费者消费的时候,先拿id去比如redis里查一下,之前若没消费过,就处理id写redis,若消费过,就不处理了,保证别重复消费相同 的消息即可;
方法4,或者基于数据库的唯一键来保证重复数据不会重复插入多条,跟方法1类似。
5.spring框架知识
1.springMVC运行过程
1.用户发起请求到前端控制器(Controller)
2.前端控制器没有处理业务逻辑的能力,需要找到具体的模型对象处理(Handler),
到处理器映射器(HandlerMapping)中查找Handler对象(Model),返回到前端控制器。
HandlerMapping返回执行链,包含了2部分内容: ① Handler对象、② 拦截器数组
3.前端控制器通过处理器适配器包装后执行Handler对象。
4.处理业务逻辑。
5.Handler处理完业务逻辑,返回ModelAndView对象,其中view是视图名称,不是真正的视图对象。
6.将ModelAndView返回给前端控制器。
7.视图解析器(ViewResolver)返回真正的视图对象(View)。
(此时前端控制器中既有视图又有Model对象数据)前端控制器根据模型数据和视图对象,进行视图渲染。
8.返回渲染后的视图(html/json/xml)返回。
9.给用户产生响应。
2.spring事务 隔离级别 5
ISOLATION_DEFAULT:默认隔离级别
ISOLATION_READ_UNCOMMITTED(未提交读):会出现幻读、脏读、不可重复读
ISOLATION_READ_COMMITTED(提交读):造成幻读、不可重复读
ISOLATION_REPEATABLE_READ(可重复读):但会出现幻读
ISOLATION_SERIALIZABLE(序列化):防止脏读、不可重复读、幻读
Mysql 默认:可重复读
Oracle 默认:读已提交
3.spring线程池的作用
threadPoolTaskExecutor 1.降低资源消耗
2.提高响应速度
3.提高线程的可管理性
4.spring事务 传播行为 7
PROPAGATION_REQUIRED:支持当前事务,如当前没有事务,则新建一个。
PROPAGATION_SUPPORTS:支持当前事务,如当前没有事务,则已非事务性执行。
PROPAGATION_MANDATORY:支持当前事务,如当前没有事务,则抛出异常。
PROPAGATION_REQUIRES_NEW:始终新建一个事务,如当前原来有事务,则把原事务挂起。
PROPAGATION_NOT_SUPPORTED:不支持当前事务,始终已非事务性方式执行,如当前事务存在,挂起该事务。
PROPAGATION_NEVER:不支持当前事务;如果当前事务存在,则引发异常。
PROPAGATION_NESTED:如果当前事务存在,则在嵌套事务中执行,如果当前没有事务,则执行与
PROPAGATION_REQUIRED 类似的操作。
5.spring boot和spring区别
1.约定大于配置
2.自带tomcat
3.自动配置
4.继承大量的第三方配置
6.springIOC三种注入方式
1.接口注入:如果采用接口注入一个Bean,那么通过注入的Bean就必须要实现这个接口(最差)
2.set方法注入:如果采用set注入一个Bean,那么只需要为Bean中所需要的一些组件提供set方法就可以,
通过set方法注入比较清晰,大家一看就知道(补充)
3.构造器注入:如果采用构造器注入方式,那么首先为这个Bean提供自定义的构造函数,
构造函数中需要的参数就是类中的组件实例。(最优)
7.spring是什么?spring的ioc和aop
spring是ioc和aop的轻量级容器
ioc:控制反转,用依赖注入(反射机制),将实例的初始化交给spring容器来管理,
ioc的反射机制允许我们不重新编译代码,因为它的对象是动态生成的。
aop是面向切面编程,是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,
便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
用于权限、日志 (适配器模式)
AOP基于动态代理,动态代理分为JDK动态代理和CGLIB动态代理,
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP,可以强制使用CGLIB实现AOP
2、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
8.有没有看过spring源码
9.有没有看过mybatis源码
①、一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
②、二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
6.数据库
1.高并发查询 数据库优化
1、优化sql
2、分库分表
3、读写分离
4、nosql数据库
5、缓存
6、索引
怎么优化sql?
1.建立索引(explain字段查询索引有没有用到,索引不是越多越好)
2.查询数量多的时候使用LIMIT
3.避免 SELECT *
4.用IN来替换OR
5.LIKE双百分号无法使用到索引
2.mysql怎么分库分表?怎么设计?怎么读写分离?
取模 1、中间变量 = user_id%(库数量*每个库的表数量);
2、库序号 = 取整(中间变量/每个库的表数量);
3、表序号 = 中间变量%每个库的表数量;
例如:数据库有256 个,每一个库中有1024个数据表,用户的user_id=262145,按照上述的路由策略,可得:
1、中间变量 = 262145%(256*1024)= 1;
2、库序号 = 取整(1/1024)= 0;
3、表序号 = 1%1024 = 1;
这样的话,对于user_id=262145,将被路由到第0个数据库的第1个表中。
读写分离:主从结构
1.当master正常时,读操作发生在两个slave上,写操作发生在master上。
master通过主从配置实时同步数据到两个slave上。
2.当master挂了,为了数据的一致性,和master为主从关系的slave2被停止访问。
这时slave1负责读和写,当master重新恢复后,slave1同步数据给master,然后再次恢复最初读写状态。
3.varchar和text区别,text的长度
varchar,text为可变长
text存储可变长度的非Unicode数据,最大长度为2^31-1个字符。
4.数据库四大特性
ACID
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持久性(Durability)
5.数据库索引
普通索引:最基本的索引,没有任何限制
唯一索引:与"普通索引"类似,不同的就是:索引列的值必须唯一,但允许有空值。
主键索引:它 是一种特殊的唯一索引,不允许有空值。
全文索引:仅可用于 MyISAM 表,针对较大的数据,生成全文索引很耗时好空间。
组合索引:为了更多的提高mysql效率可建立组合索引,遵循”最左前缀“原则。
索引并不是越多越好,是要占磁盘空间的。
Mysql索引主要有两种结构:B+树和hash.
hash:
优点:当查找某一条记录的时候,速度非常快.当时因为是hash结构,每个键只对应一个值
缺点:是散列的方式分布.所以他并不支持范围查找和排序等功能.
B+树:
优点:数据结构以平衡树的形式来组织,因为是树型结构,所以更适合用来处理排序,范围查找等功能.
相对hash索引,B+树在查找单条记录的速度虽然比不上hash索引,但是因为更适合排序等操作
索引失效的几种情况
1.如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)
要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引
2.对于多列索引,不是使用的第一部分,则不会使用索引
3.like查询以%开头
4.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
5.如果mysql估计使用全表扫描要比使用索引快,则不使用索引
6.外键怎么设计
主键是能确定一条记录的唯一标识。
外键用于与另一张表的关联,是能够确定令一张表记录的字段,用于保持数据的一致性。
有外键的时候,数据库会自动帮你检查A的b是否在B的b中存在。 --尽量不用
删除有外键关联表的时候,先禁用外键约束,set foreign_key_checks=0;
再删除数据,再启动外键约束,set foreign_key_checks=1
7.为何说外键有性能问题:
1.数据库需要维护外键的内部管理;
2.外键等于把数据的一致性事务实现,全部交给数据库服务器完成;
3.有了外键,当做一些涉及外键字段的增删,
更新操作之后,需要触发相关操作去检查,而不得不消耗资源;
4.外键还会因为需要请求对其他表内部加锁而容易出现死锁情况;
8.数据库的事务隔离级别
ISOLATION_READ_NOT_COMMITTED(读未提交):会出现幻读、脏读、不可重复读
ISOLATION_READ_COMMITTED(读已提交):造成幻读、不可重复读(oracle默认)
ISOLATION_REPEATABLE_READ(可重复读):但会出现幻读 (mysql默认)
ISOLATION_SERIALIZABLE(序列化):防止脏读、不可重复读、幻读
9.什么是脏读、不可重复读、幻读
脏读:两个并发事务,事务b读取了事务a尚未提交的数据。
不可重复读:事务a读取了数据,事务b修改,事务a再读,就发现不一样。
幻读:一个事务修改所有,另一个事务新增,发现有一个没有改掉。
10.选用的mysql数据库存储引擎?
1)InnoDB:支持事务安全表(ACID),支持行锁定和外键是事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键
2)MyISAM:拥有较高的插入、查询速度,但不支持事务,不支持外键。
3)MEMORY:将表中的数据存储到内存中,为查询和引用其他表数据提供快速访问
InnoDB: 支持事务处理,支持外键,支持崩溃修复能力和并发控制。如果需要对事务的完整性要求比较高
(比如银行),要求实现并发控制(比如售票),那选择InnoDB有很大的优势。如果需要频繁的更新、删除
操作的数据库,也可以选择InnoDB,因为支持事务的提交(commit)和回滚(rollback)。
MyISAM: 插入数据快,空间和内存使用比较低。如果表主要是用于插入新记录和读出记录,那么选择
MyISAM能实现处理高效率。如果应用的完整性、并发性要求比较低,也可以使用。
MEMORY: 所有的数据都在内存中,数据的处理速度快,但是安全性不高。如果需要很快的读写速度,
对数据的安全性要求较低,可以选择MEMOEY。它对表的大小有要求,不能建立太大的表。所以,
这类数据库只使用在相对较小的数据库表。
同一个数据库也可以使用多种存储引擎的表。如果一个表要求比较高的事务处理,可以选择InnoDB。
这个数据库中可以将查询要求比较高的表选择MyISAM存储。如果该数据库需要一个用于查询的临时表,
可以选择MEMORY存储引擎。
11.存放表情字段的表怎么设计?
utf8mb4
12.mysql去重
用where,紧接着group by 分组,再次筛选则用having
eg:查询选课在三门以上且各门课程均及格的学生的学号及其总成绩,查询结果按总成绩降序列出。
程序清单如下:
SELECT SNO,SUM(SCORE) AS TotalScore
FROM SC
WHERE SCORE>=60
GROUP BY SNO
HAVING COUNT(*)>=3
ORDER BY SUM(SCORE) DESC
13.什么是锁、行锁、表锁?
锁:是协调多个进程或线程并发访问某一资源的一种机制。
表锁:开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低
行锁:开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高
页锁:开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般
Myisam:共享锁(读)、排他锁(写)。
Innodb:行锁是基于索引实现的,如果不通过索引访问数据,InnoDB会使用表锁。
14.binlog日志
Mysql的binlog日志作用是用来记录mysql内部增删改查等对mysql数据库有更新的内容的记录(对数据库的改动),对数据库的查询select或show等不会被binlog日志记录;主要用于数据库的主从复制以及增量恢复。
MySQL binlog的三种工作模式
(1)Row level
日志中会记录每一行数据被修改的情况,然后在slave端对相同的数据进行修改。
优点:能清楚的记录每一行数据修改的细节
缺点:数据量太大
(2)Statement level(默认)
每一条被修改数据的sql都会记录到master的bin-log中,slave在复制的时候sql进程会解析成和原来master端执行过的相同的sql再次执行
优点:解决了 Row level下的缺点,不需要记录每一行的数据变化,减少bin-log日志量,节约磁盘IO,提高新能
缺点:容易出现主从复制不一致
(3)Mixed(混合模式)
结合了Row level和Statement level的优点
7.分布式
1.什么是分布式系统?用过那些分布式系统?
一个业务分拆多个子业务,部署在不同的服务器上。
2.分布式锁
1.基于数据库实现分布式锁
2.基于缓存redis实现分布式锁 (设置超时时间)
3.基于Zookeeper实现分布式锁(临时节点)
3.分布式缓存,
redis、Memcached
redis和Memcached区别
1 、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
2 、Redis支持数据的备份,即master-slave模式的数据备份。
3 、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
4 、redis是单线程,memcached是多线程
4.分布式redis缓存击穿
1、使用互斥锁排队:根据key获取value值为空时,锁上,从数据库中load数据后再释放锁。
若其它线程获取锁失败,则等待一段时间后重试。
2、接口限流与熔断、降级:重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,
当接口中的某些服务不可用时候,进行熔断,失败快速返回机制(Hystrix:服务熔断,服务降级)
3、布隆过滤器:bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,
其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。
5.分布式缓存雪崩
缓存在同一时间内大量键过期(失效),接着来的一大波请求瞬间都落在了数据库中导致连接异常。
解决方案:
方案1、也是像解决缓存穿透一样加锁排队,实现同上;
方案2、建立备份缓存,缓存A和缓存B,A设置超时时间,B不设值超时时间,先从A读缓存,A没有读B,
并且更新A缓存和B缓存;
方案3、设置缓存超时时间的时候加上一个随机的时间长度,比如这个缓存key的超时时间是固定的5分钟
加上随机的2分钟,酱紫可从一定程度上避免雪崩问题;
5.分布式框架(dubbo、springcloud)
eureka:服务发现注册
ribbon:负载均衡
feign:远程调用
hystrix:熔断
zuul:过滤路由
dubbo架构:
0 服务容器负责启动,加载,运行服务提供者。
1. 服务提供者(生产者)在启动时,向注册中心注册自己提供的服务。
2. 服务消费者在启动时,向注册中心订阅自己所需的服务。
3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
6.分布式会话共享(session),分布式Session一致性?(单点登录问题)
session同步法:
1.session复制(同步):多个web-server之间相互同步session,这样每个web-server之间都包含全部
的session
2.客户端存储法:服务端存储所有用户的session,内存占用较大,可以将session存储到浏览器cookie中,
每个端只要存储一个用户的数据了
3.后端统一存储:将session存储在web-server后端的存储层,数据库或者缓存 (最佳方法)
7.CAP原则(zookeeper、eureka)
C:一致性
A:可用性
P:分区容错性
CAP原则又称CAP定理,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、
Partition tolerance(分区容错性),三者不可得兼,是NOSQL数据库的基石。
只能:
CP:放弃可用性,追求一致性和分区容错性 (zookeeper)
AP:放弃一致性,追求放弃容错性和可用性(eureka)
8.BASE 原则
BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)
三个短语的缩写,是对 CAP 中 AP 的一个扩展。
基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。
软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是 CAP 中的不一致。
最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。
9.分布式事务
两阶段提交方案/XA方案
所谓的 XA 方案,即:两阶段提交,有一个事务管理器的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。
这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。如果要玩儿,那么基于 Spring + JTA
就可以搞定,自己随便搜个 demo 看看就知道了。
这个方案,我们很少用,一般来说某个系统内部如果出现跨多个库的这么一个操作,是不合规的。我可以给大家介绍一下, 现在微服务,一个大的系统分成几十个甚至几百个服务。一般来说,我们的规定和规范,是要求每个服务只能操作自己对应的一个数据库。
如果你要操作别的服务对应的库,不允许直连别的服务的库,违反微服务架构的规范,你随便交叉胡乱访问,几百个服务的话,全体乱套,这样的一套服务是没法管理的,没法治理的,可能会出现数据被别人改错,自己的库被别人写挂等情况。
如果你要操作别人的服务的库,你必须是通过调用别的服务的接口来实现,绝对不允许交叉访问别人的数据库。
TCC 方案
TCC 的全称是:Try
、Confirm
、Cancel
。
- Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留。
- Confirm 阶段:这个阶段说的是在各个服务中执行实际的操作。
- Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚)
这种方案说实话几乎很少人使用,我们用的也比较少,但是也有使用的场景。因为这个事务回滚实际上是严重依赖于你自己写代码来回滚和补偿了,会造成补偿代码巨大,非常之恶心。
比如说我们,一般来说跟钱相关的,跟钱打交道的,支付、交易相关的场景,我们会用 TCC,严格保证分布式事务要么全部成功,要么全部自动回滚,严格保证资金的正确性,保证在资金上不会出现问题。
而且最好是你的各个业务执行的时间都比较短。
但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码是很难维护的。
可靠消息最终一致性方案
直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。
大概的意思就是:
- A 系统先发送一个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作别执行了;
- 如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 mq 发送确认消息,如果失败就告诉 mq 回滚消息;
- 如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务;
- mq 会自动定时轮询所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。
- 这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。。
服务器负载均衡:在多个提供相同服务的服务器的情况下,负载均衡设备存在虚拟服务IP地址。当大量客户端从外部访问虚拟服务IP地址时,负载均衡设备将这些报文请求根据负载均衡算法,将流量均衡的分配给后台服务器以平衡各个服务器的负载压力,避免在还有服务器压力较小情况下其他服务达到性能临界点出现运行缓慢甚至宕机情况,从而提高服务效率和质量。因此对客户端而言,RS(real server 实际服务器)的IP地址即是负载均衡设备VIP(虚拟服务地址IP)地址,真正的RS服务器IP地址对于客户端是不可见的。
反向代理:普通的代理设备是内网用户通过代理设备出外网进行访问,而工作在这种模式下的负载均衡设备,则是外网用户通过代理设备访问内网,因此称之为反向代理。
8.设计模式
1.单例模式
public class SingletonDemo1 {
private static SingletonDemo1 instance;
private SingletonDemo1(){}
public static SingletonDemo1 getInstance(){
if (instance == null) {
instance = new SingletonDemo1();
}
return instance;
}
}懒汉模式 不安全
public class SingletonDemo3 {
private static SingletonDemo3 instance = new SingletonDemo3();
private SingletonDemo3(){}
public static SingletonDemo3 getInstance(){
return instance;
}
}饿汉模式 安全
public class SingletonDemo7 {
private volatile static SingletonDemo7 singletonDemo7;
private SingletonDemo7(){}
public static SingletonDemo7 getSingletonDemo7(){
if (singletonDemo7 == null) {
synchronized (SingletonDemo7.class) {
if (singletonDemo7 == null) {
singletonDemo7 = new SingletonDemo7();
}
}
}
return singletonDemo7;
}
}双重校验锁 安全
2.代理模式
9.IO流
1.nio bio aio的区别
Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一
个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用
器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。(通道和缓冲区的形式来进行处理数据的。)
Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成
了再通知服务器应用去启动线程进行处理,
BIO、NIO、AIO适用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以
前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复
杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比
较复杂,JDK7开始支持。
2. NIO和IO到底有什么区别?有什么关系?
10.linux知识
1.linux常用命令?查看线程命令
ps
2.部署项目上线过程(Tomcat)
3.动态查看日志
tailf -200 /log/catalina.out
11.项目相关知识
1.近期的项目,项目介绍,用到哪些框架
2.遇到了哪些问题,怎么解决的
3.ES是什么?怎么实现的?分词?配置安装?分页?
数据同步方式:
1.使用插件logstash,物理删除
2.定时任务
分页;
1.浅分页 from-size
2.深度分页 scroll (分片数*size)
同义词:写在同一行
儿童,青年,少年,幼年
西红柿,番茄 => 西红柿,番茄
社保,公积金 => 社保,公积金
4.分词
IK分词支持两种分析器Analyzer: ik_smart , ik_max_word , 两种分词器Tokenizer: ik_smart , ik_max_word,
ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合;
ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。
12.其他
1.你对于未来的规划
2.常看的外国java网站
stackoverflow
3.怎么学习新的技术和新的知识点
4.印象最深的一个问题?遇到困难,是怎么解决的