java面试题

Java开发面试整理                   
 作者:不愿透露姓名的王
 qq:2314143004     E-mail:231414300@qq.com

1. 熟练掌握 Java 面向对象编程思想, 集合类的使用, IO 流(NIO、AIO、BIO), 多线程。了解 JVM 的运行原理、类加载机制。性能调优、GC 的垃圾回收原理、设计模式、反射等。
涉及:Java面向对象,集合类,IO流(NIO,AIO,BIO),多线程,线程池,反射,设计模式,JVM运行原理,类加载机制

Java面向对象思想
首先,编程语言主要分为面向过程的语言和面向对象的语言,比如C语言就是一种面向过程的语言,而我一直用的Java语言就是一门面向对象的语言,对于Java来说,一切皆是对象,把现实世界中的对象在Java中抽象的表达出来,每个对象都有他具体的属性和行为,都是一种类别的实例,对象之间通过相互作用传递信息,实现程序的开发,而Java独有的继承,封装,多态的特性,使它更为强大,这就是我对java面向对象的理解。

JDK1.8新特性
lambda 表达式:允许把函数作为参数传递到方法,简化匿名内部类代码。
函数式接口:使用 @FunctionalInterface 标识,有且仅有一个抽象方法,可被隐式转换为 lambda 表达式。
方法引用:可以引用已有类或对象的方法和构造方法,进一步简化 lambda 表达式。
接口:接口可以定义 default 修饰的默认方法,降低了接口升级的复杂性,还可以定义静态方法。
注解:引入重复注解机制,相同注解在同地方可以声明多次。注解作用范围也进行了扩展,可作用于局部变量、泛型、方法异常等。
类型推测:加强了类型推测机制,使代码更加简洁。
Optional 类:处理空指针异常,提高代码可读性。
Stream 类:引入函数式编程风格,提供了很多功能,使代码更加简洁。方法包括 forEach 遍历、count 统计个数、filter 按条件过滤、limit 取前 n 个元素、skip 跳过前 n 个元素、map 映射加工、concat 合并 stream 流等。
日期:增强了日期和时间 API,新的 java.time 包主要包含了处理日期、时间、日期/时间、时区、时刻和时钟等操作。
JavaScript:提供了一个新的 JavaScript 引擎,允许在 JVM上运行特定 JavaScript 应用。



重写和重载的区别
重写
继承重写方法,参数个数、类型一致
重载
方法同名,但是参数表不同。

集合类
Java集合类存放在java.util包中,是一个用来存放对象的容器。
Collection(接口)
List(接口)
ArrayList(类)
特点:
ArrayList是一个存取有序,可重复的集合,它是异步,线程不安全的,它的底层存储结构是Object数组,所以查询快增删慢,初始容量为10,加载因子为1,扩容倍数为1.5倍。
原理:
add() 添加:添加元素的时候会先去检查他内部当前Object数组的当前下标+1是否超出数组总长度,如果没有,就添加,如果超出了,就触发扩容机制,将原数组中的数据复制到大小为原数组1.5倍的新数组中,并将新数组赋值给原数组。
remove() 删除:ArrayList在删除的时候会先检查所删元素的下标是否为数组最后一个位置,如果是,直接将此位置设置为空,如果不是,就从这个下标加1的位置开始向后所有的元素都往前移动一个位置,然后把数组最后一个位置设置为空。.
get() 获取:直接获取数组当前下标的元素。
set() 改变:直接把元素赋值给数组当前下标的位置。

LinkedList(类)
特点:
LinkedList是一个存取有序,可重复的集合,它是异步,线程不安全的,他的底层存储结构是双向链表,所以增删快,查询慢,容量方面没有限制。
原理:
add() 添加:LinkedList有两个节点变量first和last,在添加元素时,会创建新节点,	将last赋值给新节点的上节点,下节点赋值为空,中间的值为要添加的元素,然后把新节点赋值给last,再判断一下上节点是不是为空,为空就说明LinkedList里原来是没有元素的,然后把新节点赋值给first,否则就将last的next指向新节点,使他形+成双向链表。
remove()删除:将这个节点删除,再将它相邻的两个节点的指向修改为相邻。
get()获取:是一个查找的过程,先比较下标和集合长度的右位移一位的值的大小,如果下标小,就从头部开始查找,如果下标大,就从尾部开始查找。
set()改变:先查找这个节点,比较下标和集合长度的右位移一位的值的大小,如果下标小,就从头部开始查找,如果下标大,就从尾部开始查找,找到节点后,将节点的元素指向新元素。
Set(接口)
HashSet(类)
特点:
HashSet是一个存取无序的,不可重复的集合,他是异步,线程不安全的,他的底层使用HashMap来存储,初始容量为16,加载因子为0.75,扩容倍数为2倍。
原理:
add()添加:就是调用了map的put方法,元素用于键,值是一个固定值,HashMap中一个键只会保存一份,所以重复添加HashMap不会变化,就实现了去重添加。
remove()删除:就是调用map的remove方法,移除HashMap中key为该元素的节点

LinkedHashSet(类)
特点:
LinkedHashSet是一个存取有序的,不可重复的集合,他是异步,线程不安全的,他的底层是LinkedHashMap,可以依靠双向链表维护元素插入顺序,初始容量为16,加载因子为0.75,扩容倍数为2倍。
原理:(使用HashSet的)
add()添加:就是调用了map的put方法,元素用于键,值是一个固定值,HashMap中一个键只会保存一份,所以重复添加HashMap不会变化,就实现了去重添加。
remove()删除:就是调用map的remove方法,移除HashMap中key为该元素的节点

TreeSet(类)
特点:
TreeSet是一个存取无序的,可排序的,不可重复的集合,他是异步,线程不安全的,他的底层是基于TreeMap来实现,所以数据结构是红黑树,容量方面没有限制。
原理:
add()添加:就是调用了map的put方法,元素用于键,值是一个固定值,TreeMap中一个键只会保存一份,所以重复添加HashMap不会变化,就实现了去重添加。
remove()删除:就是调用map的remove方法,移除TreeMap中key为该元素的节点
Map(接口)
HashMap()
特点:
HashMap是一个存取无序的,不可重复的KeyValue型集合,他是异步,线程不安全的,他的底层存储结构是数组+链表+红黑树,初始容量为16,加载因子为0.75,扩容倍数为2倍。
原理:
put()添加:判断新插入这个值是否导致长度已经超过了阈值,是则进行扩容,没超过走正常添加流程,先判断Key是否为null,如果为null,则把元素存储到Entry<k,v>数组的一个元素中,返回null,因为null 的哈希值是0.然后计算元素的hash 值 ,根据hash 值获取table准确的位置,遍历准确找到数组中的链表。比较hashCode 和 equals 方法检查有无碰撞,如果添加的元素与已经存在元素的 hashCode 相同并且equals 相同的话,则 把之前的value 覆盖成新加入的value,如果添加的元素和之前元素的hashCode 相同,equels不同,则数组的下标相同,会new 一个Entry对象 把 原有的对象放入 链表中,如果链表长度超过8,就转为红黑树,如果添加的元素和值之前的元素 hashCode 、equles 不相同的话,那么则会添加到table的下一个位置当中去。
remove()删除:删除指定的key,不会影响数组长度如果是数组就设为null,如果是链表就断开并删除节点再重新连接,如果是红黑树就调整树结构保持平衡,如果有必要则将红黑树缩减为链表。
get()获取:先判断这个key是否为null,如果为null,返回table下标0中的元素 null,然后调用hash 方法计算hash 值,再根据hash值计算table准确的位置,最后遍历数组的链表 :找到和key相符的并返回。

HashTable()
特点:HashTable是一个存取无序的,不可重复的KeyValue型集合,他是同步,线程安全的,他的底层存储结构是数组+单向链表,初始容量为11,加载因子为0.75,扩容倍数为2+1。
原理:
put()添加:全程用Synchronized锁的方式保证线程安全,先判断value不能为null,再遍历找到这个key,如果存在,就覆盖value的值,如果不存在,就新增,判断是否需要扩容。
remove()删除:全程用Synchronized锁的方式保证线程安全,先计算出key的hash值,再通过hash值判断位置,遍历找到链表里的节点并删除。
get()获取:全程用Synchronized锁的方式保证线程安全,先计算出key的hash值,再通过hash值判断位置,然后迭代链表,找到相对应key的value。

HashMapHashTable的区别(主要区别)
1.HashMap允许key ,value 为空
   HashTable不允许 key ,value 为空

2.HashMap是异步的,线程不安全
   HashTable因为所有操作方法都加了Synchronized锁,是同步的,所以是线程安全的

3.HashMap初始容量16,扩容为原长度*2
   HashTable初始容量11,扩容为原长度*2+1

4.HashMap没有contains方法
   HashTable提供contains方法

LinkedHashMap()
特点:
LindedHashMap是一个存取有序的,不可重复的KeyValue型集合,他是异步,线程不安全的,他的底层是额外加了一层双向链表维护有序性,存储还是用HashMap的数组+链表+红黑树,初始容量为16,加载因子为075,扩容倍数为2倍。
原理:(put和remove使用了HashMap的)
put()添加:判断新插入这个值是否导致长度已经超过了阈值,是则进行扩容,没超过走正常添加流程,先判断Key是否为null,如果为null,则把元素存储到Entry<k,v>数组的一个元素中,返回null,因为null 的哈希值是0.然后计算元素的hash 值 ,根据hash 值获取table准确的位置,遍历准确找到数组中的链表。比较hashCode 和 equals 方法检查有无碰撞,如果添加的元素与已经存在元素的 hashCode 相同并且equals 相同的话,则 把之前的value 覆盖成新加入的value,如果添加的元素和之前元素的hashCode 相同,equels不同,则数组的下标相同,会new 一个Entry对象 把 原有的对象放入 链表中,如果链表长度超过8,就转为红黑树,如果添加的元素和值之前的元素 hashCode 、equles 不相同的话,那么则会添加到table的下一个位置当中去。
remove()删除:删除指定的key,不会影响数组长度如果是数组就设为null,如果是链表就断开并删除节点再重新连接,如果是红黑树就调整树结构保持平衡,如果有必要则将红黑树缩减为链表。
get()获取:重写了HashMap的get方法,不止会取出所索要的节点的值,而且会调整LinkedHashMap中内置的链表中该键所对应的节点的位置,将该节点置为链表的尾部。
TreeMap(类)
特点:
TreeMap是一个存取无序,自然排序的,不可重复的KeyValue型集合,他是异步,线程不安全的,他的底层存储结构是红黑树,也称平衡性二叉树,容量方面没有限制。
原理:
put()添加:先把的根节点的引用赋值给当前节点,再根据二叉树的特性,找到新的节点插入的合适位置,判断有没有外部比较器,有就用外部的,没有就用自然排序Comparable比较,最后将新节点插入并平衡下来。
remove()删除:找到这个节点,设置为Null,再运行自平衡方法。
get()获取:通过二分查找法来获。

线程安全的集合
Hashtable 
Vector 
Collections包装方法(synchronizedList,synchronizedSet,synchronizedMap)
java.util.concurrent包中的集合(ConcurrentHashMap,CopyOnWriteArrayList,CopyOnWriteArraySet)
ConcurrentHashMap
线程安全的的HashMap,初始容量为16,加载因子为0.75,扩容倍数为2倍。
原理:
jdk 1.5 
采用了分段锁策略,内部维护了一个Segment 数组,Segment继承了ReentrantLock,所以它就是一种可重入锁。在ConcurrentHashMap中,一个Segment就是一个子哈希表,Segment里维护了一个HashEntry数组,并发环境下,对于不同Segment的数据进行操作是不用考虑锁竞争的。这样既能保证性能又能保证数据的安全性
jdk 1.8
ConcurrentHashMap取消了segment分段锁,而采用CAS算法和synchronized来保证并发安全。数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升了很多。

CopyOnWriteArrayList 
线程安全的List
原理:
add()添加:
先copy出一个容器,再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址,但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据。finally中释放锁
remove()删除:
先判断要删除的元素是否最后一个,是的话直接复制长度为旧数组的length-1即可;
如果不是,就先复制旧的数组的index前面元素到新数组中,然后再复制
*;旧数组中index后面的元素到数组中,最后再把新数组赋值给旧数组。finally中释放锁
set()改变:
先获取就数组要修改的值,判断值是否改变,改变了则copa老数组得到一个新数组,然后更新改变的值的下标位置的值,最后再把新数组赋值给旧数组,finally中释放锁
CopyOnWriteArraySet
线程安全的Set,底层是一个CopyOnWriteArrayList 
原理:
add()添加:主要是调用CopyOnWriteArrayList.addIfAbsent方法
remove()删除:主要是调用CopyOnWriteArrayList.remove方法

Collections(集合工具类)
常用方法
Shuffle()随机排序
Reverse()反转排序
Sort()自然比较
copy()复制集合
swap()交换两个位置的元素位置

CollectionCollections的区别
Collection
是集合类的上层接口。本身是一个Interface,里面包含了一些集合的基本操作。Collection接口时Set接口和List接口的父接口

Collections 
Collections是一个集合框架的帮助类,里面包含一些对集合的排序,搜索以及序列化的操作。最根本的是Collections是一个类,Collections 是一个包装类,Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许,一些 collection 是有序的,而另一些则是无序的。

CAS算法
采用单cpu指令执行,CAS算法中有三个参数 V:要更新的值 E 表示预期值 N 表示新值,只有当v 等于E 的时候, 才会将N付给 V ,如果不相等,那么 要么什么都不做,要么就进行无限重试,直到v==e 结束。

Java八大基本数据类型
整型
byte(1字节) 包装类Byte
short(2字节) 包装类Short
int(4字节) 包装类Integer
long(8字节) 包装类Long
字符型
char(2字节) 包装类Character
浮点型
float(4字节) 包装类Float
double(8字节) 包装类Double
布尔型
boolean(4字节) 包装类Boolean

非常注意:String不是基本数据类型,他是引用数据类型

抽象类和接口的区别
抽象类要被子类继承,接口要被类实现。
接口在jdk 1.7只能做方法声明,在1.8 之后可以有方法的实现,抽象类中可以作方法声明,也可以做方法实现。
抽象类只能单继承,接口可以多继承
抽象类有构造器,但是不能实例化,接口没有构造器

接口和抽象类的选择
1.优先使用接口
2.在既要定义子类的行为,又要为子类提供公共的功能时应选择抽象类。

String
常用方法
length()字符串的长度
charAt()截取一个字符
replace()替换
getBytes()将字符串变成一个byte数组
toCharArray()将字符串变成一个字符数组
equals()比较两个字符串是否相等,区分大小写
equalsIgnoreCase()比较两个字符串是否相等,不区分大小写
startsWith()判断字符串是不是以特定的字符开头
endsWith()判断字符串是不是以特定的字符结束
toUpperCase()将字符串转换为大写
toLowerCase()将字符串转换为小写
concat() 连接两个字符串
trim()去掉起始和结束的空格
substring()截取字符串
split()分割字符串返回一个数组
indexOf()查找字符或字符串第一次出现的地方
lastIndexOf()查找字符或字符串最后一次出现的地方
compareTo()按字典顺序比较两个字符串的大小,区分大小写
compareToIgnoreCase()按字典顺序比较两个字符串的大小,不区分大小写

StringStringBuffer的区别
1.String是常量,StringBuffer是变量
2.String处理字符串时每次都重新赋值,StringBuffer处理字符串时可以直接修改,不必生成新的对象
3.String是线程不安全的,,StringBuffer是线程安全的

stringbuffer和stringbuilder的区别
1、线程安全:StringBuffer:线程安全,StringBuilder:线程不安全。因为StringBuffer的所有公开方法都是synchronized修饰的,StringBuilder并没有StringBuilder修饰。
2、缓冲区:StringBuffer每次获取toString都会直接使用缓存区的toStringCache值来构造一个字符串。而StringBuilder则每次都需要复制一次字符数组,再构造一个字符串。所以,缓存冲这也是对 StringBuffer 的一个优化吧,不过 StringBuffer 的这个toString 方法仍然是同步的。
3、性能:StringBuffer是线程安全的,它所有公开方法都是同步的,StringBuilder是没有对方法加锁同步的,所以毫无疑问,StringBuilder的性能要远大于 StringBuffer。

IO流(NIO,AIO,BIO)
流的概念
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。它的特性是进行数据传输;
IO流分类
主要分为字符流和字节流,字符流一般用于文本文件,字节流一般用于图像或其他文件。
字符流包括了字符输入流 Reader 和字符输出流 Writer,字节流包括了字节输入流 InputStream 和字节输出流 OutputStream。字符流和字节流都有对应的缓冲流,字节流也可以包装为字符流,缓冲流带有一个 8KB 的缓冲数组,可以提高流的读写效率。除了缓冲流外还有过滤流 FilterReader、字符数组流 CharArrayReader、字节数组流 ByteArrayInputStream、文件流 FileInputStream 等。
BIO
BIO 是同步阻塞式 IO,JDK1.4 前的 IO 模型。服务器实现模式为一个连接请求对应一个线程,服务器需要为每一个客户端请求创建一个线程,如果这个连接不做任何事会造成不必要开销。可以通过线程池改善,称为伪异步 IO。适用连接数目少且服务器资源多的场景。
NIO
NIO 是 JDK1.4 引入的同步非阻塞 IO。服务器实现模式为多个连接请求对应一个线程,客户端连接请求会注册到一个多路复用器 SelectorSelector 轮询到连接有 IO 请求时才启动一个线程处理。适用连接数目多且连接时间短的场景。
同步是指线程还是要不断接收客户端连接并处理数据,非阻塞是指如果一个管道没有数据,不需要等待,可以轮询下一个管道。
核心组件:
Selector: 多路复用器,轮询检查多个 Channel 的状态,判断注册事件是否发生,即判断 Channel 是否处于可读或可写状态。使用前需要将 Channel 注册到 Selector,注册后会得到一个 SelectionKey,通过 SelectionKey 获取 ChannelSelector 相关信息。
Channel: 双向通道,替换了 BIO 中的 Stream 流,不能直接访问数据,要通过 Buffer 来读写数据,也可以和其他 Channel 交互。
Buffer: 缓冲区,是一块可读写数据的内存。Buffer 三个重要属性:position 下次读写数据的位置,limit 本次读写的极限位置,capacity 最大容量。
flip 将写转为读,底层实现原理把 position 置 0,并把 limit 设为当前的 position 值。
clear 将读转为写模式(用于读完全部数据的情况,把 position 置 0,limit 设为 capacity)。
compact 将读转为写模式(用于存在未读数据的情况,让 position 指向未读数据的下一个)。
通道方向和 Buffer 方向相反,读数据相当于向 Buffer 写,写数据相当于从 Buffer 读。
使用步骤:向 Buffer 写数据,调用 flip 方法转为读模式,从 Buffer 中读数据,调用 clear 或 compact 方法清空缓冲区。
AIO
AIO 是 JDK7 引入的异步非阻塞 IO。服务器实现模式为一个有效请求对应一个线程,客户端的 IO 请求都是由操作系统先完成 IO 操作后再通知服务器应用来直接使用准备好的数据。适用连接数目多且连接时间长的场景。
异步是指服务端线程接收到客户端管道后就交给底层处理IO通信,自己可以做其他事情,非阻塞是指客户端有数据才会处理,处理好再通知服务器。
实现方式包括通过 Future 的 get 方法进行阻塞式调用以及实现 CompletionHandler 接口,重写请求成功的回调方法 completed 和请求失败回调方法 failed。

应用场景
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
创建对象的四种方式
1.new一个对象创建
2.调用对象的clone方法创建
3.利用反射,调用Class类的或者是Constructor类的newInstance方法创建
4.用反序列化,调用ObjectInputStream类的readObject方法创建

浅克隆和深克隆                                       
浅克隆:仅仅复制所克隆的对象,而不复制它所引用的对象。
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。
深克隆:把要复制的对象所引用的对象都复制了一遍。
那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。

static的作用
static主要有5中使用情况:成员变量、成员方法、代码块,内部类和静态导包。
static修饰成员变量:该成员变量属于类变量,可以通过 ClassName.attributeName 直接引用,而不需要new出类的实例。
static修饰成员方法:该方法属于类的方法,可以通过 ClassName.methodName 直接引用,而不需要new出类的实例。
static修饰代码块:仅在类初始化的时候执行一次,且加载顺序是严格按照类中静态资源的定义顺序来加载的;静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问。;父类代码块->子类代码块。
static修饰内部类:static不能修饰普通类,只能修饰内部类,被static修饰的内部类的创建方式: new OuterClass.InnerClass()static导入包:语法“import static java.lang.Math.*”,这样在类中就可以直接使用Math类中的静态方法而不需要写类名,在频繁使用某个类的时候比较方便,但是降低了可读性。并且不建议导入*

final的作用
当final修饰类的时候,说明该类不能被继承
当final修饰方法的时候,说明该方法不能被重写用来修饰类
当final修饰成员变量时,有两种情况:
如果修饰的是基本类型,说明这个变量的所代表数值永不能变(不能重新赋值)!
如果修饰的是引用类型,该变量所的引用不能变,但引用所代表的对象内容是可变的!

Object的常用方法
1,构造函数 
2,hashCode和equale函数用来判断对象是否相同, 
3wait(),wait(long),wait(long,int),notify(),notifyAll() 
4toString()和getClass, 
5clone() 
6finalize()用于在垃圾回收

java并发包juc包下常用的类
瑞安踹特路可
ReentrantLock,重入锁

凯可瑞特哈希卖普
ConcurrentHashMap 线程安全的Map

考配昂外特额瑞里四特
CopyOnWriteArrayList 线程安全的List

考配昂外特额瑞塞特
CopyOnWriteArraySet 线程安全的Set

开奥当辣吃
CountDownLatch 同步计数器

哎桃卖可引忒哥
AtomicInteger 原子数

赛可北儿 
CyclicBarrier 循环屏障

多线程
创建线程的方式
1.继承Thread类创建线程(单继承局限性)
2.实现Runnable接口创建线程
3.使用CallableFuture创建线程
4.使用线程池创建
线程的生命周期
线程的生命周期包含5个阶段:新建、就绪、运行、阻塞、销毁。
1.新建:使用new方法
2.就绪:调用的线程的start()方法
3.运行:run方法 
4.阻塞:sleep()wait()之后线程就处于了阻塞状态
5.销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束
线程安全和线程不安全
线程安全:在多线程环境下对共享资源做增删改的操作,保证共享资源的原子性,正确性 
线程不安全:在多线程环境下对共享资源做增删改的操作,导致共享资源的结果不正确 
synchronized的原理
当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。。
当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。
synchronized的作用
synchronized是一种同步锁,作用是防止多个线程同一时间调用此代码块或者方法.
synchronized 作用于普通方法,锁的对象是当前实例
synchronized 作用于静态方法,锁的对象是类的Class对象
synchronized 作用于方法块,锁的对象是括号里匹配的对象

ReentrantLock 工作原理
必须要用肯嘚深的额卫特和赛个闹方法
必须要用 condition 的await() signal() 
用cas算法比较状态值 由0 改为1 。如果成功则获取到锁。
如果不成功则再次尝试获取到锁,如果不成功则加入到队列中。并且挂起采用

synchronizedReentrantLock的区别
synchronized   是一个关键字
ReentrantLock   是一个类

synchronized  加锁和释放锁自动加锁和释放
ReentrantLock  必须用lock()unlock() 加锁和释放锁    

synchronized  是一个非公平锁
ReentrantLock  提供了公平锁又提供了一个非公平锁  通过构造参数 boolean 决定 是公平还是非公平

synchronized   不可限时
ReentrantLock  可限时 try 避免死锁

synchronized   不可中断 
ReentrantLock    可中断 lockInterruptibly 获取锁但是响应中断

springMVC和springBean是线程安全的么,为什么,怎么避免线程不安全
Spring管理的Bean对象默认是单例模式,当多线程操作Bean对象时就会出现线程安全问题;因为在多线程中线程改变了bean对象的可变成员变量时,其他线程就无法访问该bean对象的初始状态,这样就造成数据错乱。所以需要用线程同步来处理这个问题。

volatile
修饰符关键字:volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最新的值

线程池
线程池的作用
为了避免线程的频繁创建和销毁对性能的损耗而实现对线程的复用
线程池的种类
单线程池 
固定大小线程池
缓存线程池
定时线程池
自定义线程池
线程池的参数
int corePoolSize, 核心线程数
int maximumPoolSize,最大线程数
long keepAliveTime,空闲线程存活时间
TimeUnit unit,空闲线程存活时间单位
BlockingQueue workQueue,阻塞队列
ThreadFactory threadFactory,线程工厂
RejectedExecutionHandler handler 拒绝策略
线程池的工作原理(大白话)
首先,我们的线程先从核心线程数里面去执行,如果不够的话,会放到队列里面,如果还不够的话,临时线程数会开启,如果还不够的话,就执行拒绝策略,那么这个时候,如果够的情况下呢,比如我现在核心线程数在做,队列里面也没满,这个时候就有一个等待时间,过了这个等待时间后, 我的临时线程会被释放掉,这是他这个的工作原理
拒绝策略
额宝特抛了C
默认的拒绝策略为 AbortPolicy执行的结果是RejectedExecutionException
第四卡的抛了C
DiscardPolicy 执行结果什么都不做
第四卡的偶得抛了C
DiscardOldestPolicy 执行结果为从队列中舍去一个线程,执行当前线程
卡乐润死抛了C
CallerRunsPolicy 执行结果为如果当前线程没有终止,则运行当前线程

反射
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
哪里用到反射机制
1.JDBC中,利用反射动态加载了数据库驱动程序。
2.Web服务器中利用反射调用了Sevlet的服务方法。
3.Eclispe等开发工具利用反射动态刨析对象的类型与结构,动态提示对象的属性和方法。
4.很多框架都用到反射机制,注入属性,调用方法,如Spring。
反射机制的优缺点
优点:可以动态执行,在运行期间根据业务功能动态执行方法、访问属性,最大限度发挥了java的灵活性。
缺点:对性能有影响,这类操作总是慢于直接执行java代码。
动态代理是什么
动态代理是运行时动态生成代理类。
动态代理的应用有 Spring AOP数据查询、测试框架的后端 mock、rpc,Java注解对象获取等。
怎么实现动态代理?
JDK 原生动态代理和 cglib 动态代理。
JDK 原生动态代理是基于接口实现的,而 cglib 是基于继承当前类的子类实现的。
使用Java的反射的方式
1.通过一个全限类名创建一个对象
2.获取构造器对象,通过构造器new出一个对象
3.通过class对象获得一个属性对象
4.通过class对象获得一个方法对象o

设计模式
创建型模式
单例模式:某个类只能有一个实例,提供一个全局的访问点。
简单工厂:一个工厂类根据传入的参量决定创建出那一种产品类的实例。
工厂方法:定义一个创建对象的接口,让子类决定实例化那个类。
抽象工厂:创建相关或依赖对象的家族,而无需明确指定具体类。
建造者模式:封装一个复杂对象的构建过程,并可以按步骤构造。
原型模式:通过复制现有的实例来创建新的实例。
结构型模式
适配器模式:将一个类的方法接口转换成客户希望的另外一个接口。
组合模式:将对象组合成树形结构以表示“”部分-整体“”的层次结构。
装饰模式:动态的给对象添加新的功能。
代理模式:为其他对象提供一个代理以便控制这个对象的访问。
亨元模式:通过共享技术来有效的支持大量细粒度的对象。
外观模式:对外提供一个统一的方法,来访问子系统中的一群接口。
桥接模式:将抽象部分和它的实现部分分离,使它们都可以独立的变化。
行为型模式
模板模式:定义一个算法结构,而将一些步骤延迟到子类实现。
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器。
策略模式:定义一系列算法,把他们封装起来,并且使它们可以相互替换。
状态模式:允许一个对象在其对象内部状态改变时改变它的行为。
观察者模式:对象间的一对多的依赖关系。
备忘录模式:在不破坏封装的前提下,保持对象的内部状态。
中介者模式:用一个中介对象来封装一系列的对象交互。
命令模式:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
访问者模式:在不改变数据结构的前提下,增加作用于一组对象元素的新功能。
责任链模式:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。
设计的原则
 单一职责原则:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
 开闭原则:一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
 里氏代换原则:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
 依赖倒转原则:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
 接口隔离原则:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
 迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用。
设计模式在项目中的使用
1、模板方法模式
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,如JdbcTemplate
2、代理
spring的Proxy模式在aop中有体现
3、观察者
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
spring中Observer模式常用的地方是listener的实现。如ApplicationListener4、适配器
MethodBeforeAdviceAdapter5、策略模式
使用了java的继承和多态
案例1:加减法计算器,定义一个计算类接口,加法和减法类都实现它,加的时候传入加法对象。
案例2:导出excel,pdf,word时,分别创建不同的对象
简单理解:执行多个事情时,创建多个对象
6、单例模式
解决一个全局使用的类频繁的创建与销毁
7、工厂模式
分为三种:简单工厂,工厂方法,抽象工厂
根据“需求”生产“产品”,解耦“需求”“工厂”和“产品”。
简单工厂:通过构造时传入的标识来生产产品,不同产品都在同一个工厂中生产,每新增加一种产品,需要改工厂类,来判断,这种判断会随着产品的增加而增加,给扩展和维护带来麻烦
简单工厂项目案例:根据传入的 不同(比如1对应支付流水,2 对应订单流水),生成不同类型的流水号

Cglib实现原理
1. 生成代理类 Class 二进制字节码.
2.通过 Class.forname() 加载字节码文件, 生成 Class 对象.
3.通过反射机制获得实例构造, 并创建代理类对象

JVM运行原理
内存模型jmm
共享变量存储于主内存中,每个线程都可以访问,线程不能直接操作主内存,只有操作了本地内存中的副本,才能刷新到主内存中, 每个线程也不能操作其它线程的私有的本地内存。Java并发编程安全需要具备的三大特性:原子性、可见性和有序性。
JVM体系结构
类装载器ClassLoader:用来装载.class文件
执行引擎:执行字节码,或者执行本地方法
运行时数据区:方法区、堆、Java栈、程序计数器、本地方法栈
JVM原理
JVM是java的核心和基础,在java编译器和os平台之间的虚拟处理器。它是一种利用软件方法实现的抽象的计算机基于下层的操作系统和硬件平台,可以在上面执行java的字节码程序。java编译器只要面向JVM,生成JVM能理解的代码或字节码文件。Java源文件经编译成字节码程序,通过JVM将每一条指令翻译成不同平台机器码,通过特定平台运行。
JVM执行程序的过程
1、加载.class文件
2、管理并分配内存
3、执行垃圾收集
四步完成JVM环境
1、创建JVM装载环境和配置
2、装载JVM.dll
3、初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例
4、调用JNIEnv实例装载并处理class类。
JVM的生命周期
(1)JVM实例的诞生
当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有main函数的class都可以作为JVM实例运行的起点。
(2)JVM实例的运行
main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程。
(3)JVM实例的消亡
当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用java.lang.Runtime类或者java.lang.System.exit()来退出。
方法区
有时候也称为永久代,在方法区中,存储了每个类的信息、类中静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息以及编译器编译后的代码等。当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC,在这里进行的GC主要是方法区里的常量池和类型的卸载。当方法区域需要使用的内存超过其允许的大小时,会抛出错误信息。
在方法区中有一个非常重要的部分就是运行时常量池,用于存放静态编译产生的字面量和符号引用。运行时生成的常量也会存在这个常量池中,比如String的intern方法。它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。
堆
Java中的堆是用来存储对象实例以及数组。堆是被所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的。在JVM中只有一个堆。堆是Java垃圾收集器管理的主要区域,Java的垃圾回收机制会自动进行处理。
Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB,其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配。
堆空间分为老年代和年轻代。刚创建的对象存放在年轻代,而老年代中存放生命周期长久的实例对象。年轻代中又被分为Eden区和两个Survivor(From SpaceTo Space)。新的对象分配是首先放在Eden区,Survivor区作为Eden区和Old区的缓冲,在Survivor区的对象经历若干次GC仍然存活的,就会被转移到老年代。 当一个对象大于eden区而小于old区(老年代)的时候会直接扔到old区。 而当对象大于old区时,会直接抛出错误。
JavaJava栈也称作虚拟机栈,也就是我们常常所说的栈。JVM栈是线程私有的,每个线程创建的同时都会创建自己的JVM栈,互不干扰。
Java栈是Java方法执行的内存模型。Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表、操作数栈、指向当前方法所属的类的运行时常量池的引用、方法返回地址和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。
局部变量表:用来存储方法中的局部变量。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译期就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。
操作数栈:栈最典型的一个应用就是用来对表达式求值。在一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。
指向运行时常量池的引用:因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。
方法返回地址:当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。
程序计数器
程序计数器,也有称作为PC寄存器。
由于在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,因此,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,因此,为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。
在JVM规范中规定,如果线程执行的是非本地方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。
由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象的。
本地方法栈
JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法服务的。在JVM规范中,并没有对本地方法栈的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。


类加载器
类加载机制
什么是类加载机制
Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能。
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

运行原理
装载:根据路径找到相对应的点class文件
链接:链接又可以分为三个小的步骤
检查:检查待加载的点class文件的正确性
准备:给类的静态变量分配存储空间
解析:将符号引用转换成直接引用(忽略)
初始化:对静态变量和静态代码块执行初始化工作
使用
卸载
双亲委派机制
当某个类加载器需要加载某个点class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

内存溢出
原因
1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据; 
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收; 
3.代码中存在死循环或循环产生过多重复的对象实体; 
4.使用的第三方软件中的BUG; 
5.启动参数内存值设定的过小
解决方案
第一步,修改JVM启动参数,直接增加内存。(-Xms-Xmx参数一定不要忘记加。)
第二步,检查错误日志,查看“OutOfMemory”错误前是否有其 它异常或错误。
第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。

JVM垃圾回收机制
GC基本原理
将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停
哪些内存需要回收
JVM的内存结构包括五大区域:程序计数器、虚拟机栈、本地方法栈、堆区、方法区。其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭,因此这几个区域的内存分配和回收都具备确定性,就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆区和方法区则不一样,这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。
引用计数法
引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,就将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。
优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。
可达性分析算法
程序把所有的引用关系看作一张图,从一个节点GC ROOTS开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。
在Java语言中,可作为GC Roots的对象包括下面几种:
  a) 虚拟机栈中引用的对象(栈帧中的本地变量表);
  b) 方法区中类静态属性引用的对象;
  c) 方法区中常量引用的对象;
  d) 本地方法栈中JNI(Native方法)引用的对象。
JVM对象的引用
不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:
(1)强引用:
在程序代码中普遍存在的,类似Object obj=new Object()这类引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
(2)软引用:
用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常。
(3)弱引用:
也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
(4)虚引用:
也叫幽灵引用或幻影引用,是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。它的作用是能在这个对象被收集器回收时收到一个系统通知。
两次标记过程
无论引用计数算法还是可达性分析算法都是基于强引用而言的。
即使在可达性分析算法中不可达的对象,也并非是“非死不可”,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程。
第一次标记:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记;
第二次标记:第一次标记后接着会进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,在finalize()方法中没有重新与引用链建立关联关系的,将被进行第二次标记。
第二次标记成功的对象将真的会被回收,如果对象在finalize()方法中重新与引用链建立了关联关系,那么将会逃离本次回收,继续存活。
方法区如何判断是否需要回收
方法区主要回收的内容有:废弃常量和无用的类。对于废弃常量也可通过引用的可达性来判断,但是对于无用的类则需要同时满足下面3个条件:
(1)该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例
(2)加载该类的ClassLoader已经被回收
(3)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
常用的垃圾回收算法
标记-清除算法
标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。
复制算法
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
标记-整理算法
该算法标记阶段和标记-清除算法一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。
分代收集算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代和年轻代,在堆区之外还有一个代就是永久代,它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。
老年代的特点是每次垃圾收集时只有少量对象需要被回收,而年轻代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
年轻代的回收算法
在年轻代中jvm使用的是Mark-copy(标记-复制)算法
a)所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
b)年轻代分三个区。一个Eden区,两个 Survivor(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个 Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当另外一个Survivor区也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor区过来的对象。而且,Survivor区总有一个是空的。
c)当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。
d)新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)。
老年代的回收算法
老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact(标记-整理)算法。
a)在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
b)内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC或Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。
常见的垃圾收集器
CMS收集器(标记-清除算法)
CMS收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法。高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择。
收集过程分为如下四步:
(1). 初始标记,标记GCRoots能直接关联到的对象,时间很短。
(2). 并发标记,进行GCRoots Tracing(可达性分析)过程,时间很长。
(3). 重新标记,修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,时间较长。
(4). 并发清除,回收内存空间,时间很长。
G1
G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。
G1收集器有以下特点:
(1). 并行和并发。使用多个CPU来缩短Stop The World停顿时间,与用户线程并发执行。
(2). 分代收集。独立管理整个堆,但是能够采用不同的方式去处理新创建对象和已经存活了一段时间、熬过多次GC的旧对象,以获取更好的收集效果。
(3). 空间整合。基于标记 - 整理算法,无内存碎片产生。
(4). 可预测的停顿。能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

永久代的回收算法:
永久代也称为“方法区”,他存储class对象和字符串常量。所以这块内存区域绝对不是永久的存放从老年代存活下来的对象的。在这块内存中有可能发生垃圾回收。发生在这里垃圾回收也被称为major GC。
g1垃圾回收器和cms垃圾回收器的区别
1、四个回收步骤不一致
2、g1 比cms 安全   赛选回收阶段并行处理,不和用户线程一起工作,
3、g1 新生代采用复制算法 ,老年代采用的是 标记压缩算法,不会出现,内存碎片
4、cms 采用标记清除算法。导致内存碎片
其他的收集器
Serial收集器
新生代单线程收集器,标记和清理都是单线程,优点是简单高效。单线程一方面意味着它只会使用一个CPU或一条线程去完成垃圾收集工作,另一方面也意味着它进行垃圾收集时必须暂停其他线程的所有工作,直到它收集结束为止。是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定。

Serial Old收集器(标记-整理算法)
老年代单线程收集器,Serial收集器的老年代版本。它的优点是实现简单高效,但是缺点是会给用户带来停顿。

ParNew收集器(复制算法)
新生代收集器,可以认为是Serial收集器的多线程版本,使用多个线程进行垃圾收集,在多核CPU环境下有着比Serial更好的表现。是Server模式下的虚拟机首选的新生代收集器,其中有一个很重要的和性能无关的原因是,除了Serial收集器外,目前只有它能与CMS收集器配合工作。它默认开启的收集线程数与CPU数量相同,在CPU数量非常多的情况下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。

Parallel Scavenge收集器(复制算法)
Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法。它追求高吞吐量,高效利用CPU,主要是为了达到一个可控的吞吐量。Parallel Scavenge收集器是虚拟机运行在Server模式下的默认垃圾收集器。Parallel Scavenge收集器也被称为“吞吐量优先收集器”。
适合后台应用等对交互相应要求不高的场景。是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数。

Parallel Old收集器(标记-整理算法)
Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先。使用多线程和标记-整理(Mark-Compact)算法。

JVM调优
总体步骤
1.监控GC的状态。
2.生成堆的dump文件
3.分析dump文件
4.分析结果,判断是否需要优化
5.调整GC类型和内存分配
6.不断的分析和调整
代码调优
1:减少创建对象的数量
2:减少使用全局变量和大对象
jvm 参数调优
3:调整新生代的大小到最合适
4:设置老年代的大小为最合适
5:选择合适的GC收集器
6:将转移到老年代的对象数量降到最少
7:减少Full GC的执行时间

2. 了解使用 HTML、CSS、JavascriptJQuery、AJAX、FreeMarkerVueElementUIBootstrap、axios 前端开发框架。
涉及:Vue

Vue
是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动

3. 熟悉掌握 Java 基础技术 JspServletJdbc, 熟练掌握 Web 应用系统开发, 具备的独立开发能力。
涉及:Jsp,Servlet,Jdbc

Jsp
是一种动态网页开发技术。它使用JSP标签在HTML网页中插入Java代码。

ServletJava语言编写的服务器端程序。主要功能是和浏览器进行交互,生成页面展示
网络分层
国际标准化组织提出了OSI模型:应用层。表示层。会话层。运输层。网络层。链路层。物理层,理论完善,但复杂目不实用。学习网络原理使用五层模型:应用层、运输层、网络层、链路层和物理层。实际使用TCPIP模型:应用层、运输层、网际层和网络接口层。每层使用下层的服务来提供服务,对等层问的数据单位是协议数据单元PDU,上下层间的数据单位是服务数据单元SDU.

GET和POST的区别⭐
GET 读取一个资源,可以将 GET 数据缓存在浏览器、代理或服务端。反复 GET 不应该对访问有副作用,没有副作用被称为幂等。
POST 不是幂等的,意味着不能随意多次执行,因此不能缓存,如果尝试重新执行 POST 请求,浏览器会弹出提示框询问是否重新提交表单。
GET 请求由 url 触发,想携带参数就只能在 url 后附加。
POST 请求来自表单提交,表单数据被浏览器编码到 HTTP 请求报文的请求体中。主要有两种编码格式,一种是 application/..,用来传输简单数据;另一种是 multipart/form-data格式,用来传输文件,对二进制数据传输效率高。
从攻击的角度说,无论 GET 还是 POST 都不安全,因为 HTTP 是明文协议。
GET 长度受限于 url,而 url 的长度由浏览器和服务器决定。
POST 没有大小限制,起限制作用的是服务器的处理能力。

cookie 和 session 的区别
① cookie 只能存储 ASCII 码,而 session 可以存储任何类型的数据。
② session 存储在服务器,而 cookie 存储在客户浏览器中,容易被恶意查看。。
③ session 的运行依赖 session id,而 session id 存在 cookie 中,叫做 JSESSIONID。如果浏览器禁用了 cookie ,同时 session 也会失效(可以通过其它方式实现,比如在 url 中传递 session_id)。

JdbcJava和数据库之间的一个桥梁,是一个规范而不是一个实现,能够执行SQL语句
HTTP
HTTP 存在的问题
没有加密,无法保证通信内容不被窃听。
没有报文完整性验证,无法确保通信内容在传输中不被改变。
没有身份鉴别,无法让通信双方确认对方身份。
HTTPS 原理
HTTP over SSL,在 HTTP 传输上增加了 SSL 安全套接字层,通过机密性、数据完整性、身份鉴别为 HTTP 事务提供安全保证。SSL 会对数据进行加密并把加密数据送往 TCP 套接字,在接收方,SSL 读取 TCP 套接字的数据并解密,把数据交给应用层。HTTPS 采用混合加密机制,使用非对称加密传输对称密钥保证传输安全,使用对称加密保证通信效率。

SSO单点登录
单点登录,也叫sso ,在开发的项目中,很多时候我们会是分布式(微服务)或者集群,我们需要在一个服务登录后,其它服务不用再登录,就是一次登录处处可用,需要实现单点登录,单点登录实现方式: 用户名+密码 登录,查询数据库的用户,把这个用户对象查询出来,序列化成一个json串  ,保存到redis里面, set(token+uuid ,“用户json串”)  ,同时设置它的有效期为30分钟(1个小时,四十分钟),setex()方法来实现, 同时把这个token+uuid作为cookie的 value值保存到cookie  (token,token+uuid),同时设置的它的路径path为(“/”) 为根路径,这样其它系统就可以获取到这个cookie值(token)
JWT方式
https://www.cnblogs.com/ygyy/p/13580743.html
用户填写用户名、密码,SSO系统进行认证后,将登录状态写入SSO的session,浏览器中写入SSO域下的Cookie。
SSO系统登录完成后会生成一个Service Ticket,然后跳转到app系统,同时将ST作为参数传递给app系统。
app系统拿到ST后,从后台向SSO发送请求,验证ST是否有效。
验证通过后,app系统将登录状态写入session并设置app域下的Cookie。
跨域单点登录就完成了。

4. 熟练掌握 SpringSpringMVCMybatisSpringBoot 等常用框架, 深入理解 SpringMVC 运行原理, Spring 的 IOC、AOP 设计思想以及 MyBatis 原理,了解 SpringBoot 自动配置原理, 会使用事务或分布式事务来解决一些数据不安全的问题。
涉及:Spring(IOC/AOP)SpringMVCMybatisSringBoot,事务,分布式事务

Spring(IOC/AOP)
核心IOC
bean 的创建和维护交给spring ioc 来管理,底层用的是反射机制,项目在启动的时候spring 扫描 bean 所在的包,采用反射机制class.forname().newInstence();实例化对象存储到内部的concurrenthashmap 中。获取对象从concurrenthashmap 获取,依赖注入有三种方式  注解注入、构造器注入、属性注入
核心AOP
spring aop 通过切点指定在哪些类哪些方法进行织入横切逻辑(拦截哪些类的哪些方法),通过增强描述横切逻辑的具体实现(方法前拦截,方法后拦截、出现异常拦截、环绕拦截),通过切面把切点和增强链接到一起spring aop 默认用的是jdk 动态代理,如果代理类没有实现接口,则用cglib,如果代理类实现接口则用的事jdk动态代理cglib 采用asm 技术操作字节码 使用cglib 需要注意的是代理的方法不能加上final 修饰,jdk动态代理 需要把java 编译成 class 文件然后再操作字节码aop 应用场景 spring 事物的管理、用户操作日志、登录拦截、非法字符、敏感字的拦截过滤在spring xml 中  添加 aop 的标签  
Spring
① singleton ② prototype ③ request ④ session ⑤ global-session
spring常用注解
@Autowired
spring是如何管理事务的
Spring既支持编程式事务管理,也支持声明式的事务管理

Spring组件
Bean组件
作用
    Bean组件在Spring中的Beans包下,为了解决三件事。Bean的创建,Bean的定义,Bean的解析。最关心的就是Bean的创建。

Context组件
作用
    在Spring中的context包下,为Spring提供运行环境,用以保存各个对象状态。
    Context作为Spring的IOC容器,整合了大部分功能或说大部分功能的基础,完成了以下几件事:
      1、标识一个应用环境
      2、利用BeanFactory创建Bean对象
      3、保存对象关系表
      4、能够捕获各种事件

Core组件
作用
Core是一个建立和维护每 个Bean之间的关系所需要的一些列的工具

ControllerRestController有什么区别
@RestController注解 == @ResponseBody@Controller 
1) 如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp页面,或者html,配置的视图解析器 InternalResourceViewResolver不起作用,返回的内容就是Return 里的内容。可以用做ajax请求数据等
2) 如果需要返回到指定页面,则需要用 @Controller配合视图解析器InternalResourceViewResolver才行。
    如果需要返回JSON,XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。
 
SpringMVC
springMVC组件
  DispatcherServlet 前端控制器
  HandlerMapping 请求的派发 负责让请求 和 控制器建立一一对应的关联
  Controller 真正的处理器
  ModelAndView 封装模型信息和视图信息的
  ViewResolver 视图处理器 最终定位页面的
运行原理:
1.用户发送请求至前端控制器。
2.前端控制器收到请求调用处理器映射器和处理器适配器。
3.Controller执行完成返回视图模型
4.前端控制器将视图模型传给视图解析器
5.View进行渲染视图
MVC分别代表什么和作用
模型(Model)
模型应用程序主体部分模型表示业务数据或者业务逻辑。
视图(View)
视图应用程序用户界面相关部分用户看并与之交互界面。
控制器(Controller)
控制器工作根据用户输入控制用户界面数据显示和更新model对象状态。
常用注解:
@RequestMapping:用于处理请求地址映射,可以作用于类和方法上。
    value:定义request请求的映射地址
    method:定义地request址请求的方式,包括【GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.】默认接受get请求,如果请求方式和定义的方式不一样则请求无法成功。
    params:定义request请求中必须包含的参数值。
    headers:定义request请求中必须包含某些指定的请求头,如:RequestMapping(value = "/something", headers = "content-type=text/*")说明请求中必须要包含"text/html", "text/plain"这中类型的Content-type头,才是一个匹配的请求。
    consumes:定义请求提交内容的类型。
    produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回
@RequestParam:用于获取传入参数的值
     value:参数的名称
    required:定义该传入参数是否必须,默认为true,(和@RequestMapping的params属性有点类似)
@PathViriable:用于定义路径参数值
   value:参数的名称
   required:定义传入参数是否为必须值
@ResponseBody:作用于方法上,可以将整个返回结果以某种格式返回,如json或xml格式。
@CookieValue:用于获取请求的Cookie@RequestBody:用于获取请求的对象值

SpringMVC文件上传流程
1.form表单改类型,改成二进制上传
2.mvc用媒介对象来接收,拿到file对象,拿到inputstream流
3.生成uuid存到本地,路径和现文件名和原文件名存到数据库
4.下载的时候,根据数据库的路径下载,重命名为原文件名

Mybatis
运行原理:
1、加载mybatis全局配置文件(数据源、mapper映射文件等),解析配置文件,MyBatis基于XML配置文件生成Configuration,和一个个MappedStatement(包括了参数映射配置、动态SQL语句、结果映射配置),其对应着<select | update | delete | insert>标签项。6
2SqlSessionFactoryBuilder通过Configuration对象生成SqlSessionFactory,用来开启SqlSession3SqlSession对象完成和数据库的交互:
a、用户程序调用mybatis接口层api(即Mapper接口中的方法)
b、SqlSession通过调用api的Statement ID找到对应的MappedStatement对象
c、通过Executor(负责动态SQL的生成和查询缓存的维护)将MappedStatement对象进行解析,sql参数转化、动态sql拼接,生成jdbc 
#{}和${}的区别
#{}是预编译处理,${}是字符串替换。
Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
Mybatis在处理${}时,就是把${}替换成变量的值。
使用#{}可以有效的防止SQL注入,提高系统安全性。
实体类中的属性名和表中的字段名不一样
1.sql里给字段起别名来匹配实体类属性
2.通过<resultMap>标签里的property和column属性来对实体类属性和数据库字段来一一映射
动态sql标签
trim | where | set | foreach | if | choose | when | otherwise | bind
ResultMap 和 resultType的区别9
一、对象不同
1、resultmap:resultMap如果查询出来的列名和pojo的属性名不dao一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。
2、resulttype:resultType使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。

二、描述不同
1、resultmap:resultMap对于一对一表连接的处理方式通常为在主表的pojo中添加嵌套另一个表的pojo,然后在mapper.xml中采用association节点元素进行对另一个表的连接处理。
2、resulttype:resultType无法查询结果映射到pojo对象的pojo属性中,根据对结构集查询遍历的需要选择使用resultType还是resultMap。

三、类型适用不同
1、resultmap:mybatis中在查询进行select映射的时候,返回类型可以用resultType,也可以用resultMap。
2、resulttype:resultType是直接表示返回类型的,而resultMap则是对外部ResultMap的引用,但是resultType跟resultMap不能同时存在。
MyBatis和JPA的区别
(1Mybatis和jpa不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。
(2Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。 
(3)jpa 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用JPA 开发可以节省很多代码,提高效率。

SpringBoot
优势
约定优于配置.
工作原理
Spring Boot 在启动的时候,按照约定去读取 Spring Boot Starter 的配置信息,再根据配置信息对资源进行初始化,并注入到 Spring 容器中。这样 Spring Boot 启动完毕1,就已经准备好了一切资源,使用过程中直接注入对应 Bean 资源即可
springboot和springmvc的区别
springboot是约定大于配置,可以简化spring的配置流程
springmvc是基于servlet的mvc框架
springboot的常用注解
• 项目配置注解
@SpringBootApplication 注解
查看源码可发现,@SpringBootApplication是一个复合注解,包含了@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan这三个注解。
这三个注解的作用分别为:
@SpringBootConfiguration:标注当前类是配置类,这个注解继承自@Configuration。并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到srping容器中,并且实例名就是方法名。
@EnableAutoConfiguration:是自动配置的注解,这个注解会根据我们添加的组件jar来完成一些默认配置,我们做微服时会添加spring-boot-starter-web这个组件jar的pom依赖,这样配置会默认配置springmvc 和tomcat。
@ComponentScan:扫描当前包及其子包下被@Component@Controller@Service@Repository注解标记的类并纳入到spring容器中进行管理。等价于<context:component-scan>的xml配置文件中的配置项。
springboot的自动配置原理
Spring Boot 项目的启动注解是:@SpringBootApplication,其实它就是由下面三个注解组成的:
@Configuration
@ComponentScan
@EnableAutoConfiguration
其中 @EnableAutoConfiguration 是实现自动配置的入口,该注解又通过 @Import 注解导入了AutoConfigurationImportSelector,在该类中加载 META-INF/spring.factories 的配置信息。然后筛选出以 EnableAutoConfiguration 为 key 的数据,加载到 IOC 容器中,实现自动配置功能。

事务
事务的特性
原子性、一致性、隔离性、持久性
注解参数
Propagation 事务传播行为
Timeout 事务超时设置
Isolation 事务隔离级别
事务的传播行为
PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
PROPAGATION_REQUIRED_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
PROPAGETION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。
Spring事务的隔离级别
ISOLATION_DEFAULT: 这是一个默认的隔离级别
使用数据库默认的事务隔离级别. 另外四个与JDBC的隔离级别相对应 
ISOLATION_READ_UNCOMMITTED: 读未提交 
这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。 这种隔离级别会产生脏读,不可重复读和幻像读。 
ISOLATION_READ_COMMITTED: 读已提交 
oracle 默认 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据 (防止脏读) 
ISOLATION_REPEATABLE_READ: 可重复读 
mysql 默认 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。 它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。 
ISOLATION_SERIALIZABLE 串行化 
这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻像读

分布式事务
使用场景 
不同服务之间用进行事务控制
五种方案
XA 方案   两阶段提交  垂直分库
TCC 方案  三阶段提交   用开源框架多一些
本地消息表    近几年用的多一些
可靠消息最终一致性方案    用mq 做的 大厂用这个比较多
最大努力通知方案  		分场合用

两阶段提交方案/XA方案
两阶段提交,有一个事务管理器的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。
这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。
这个方案,我们很少用,一般来说某个系统内部如果出现跨多个库的这么一个操作,是不合规的。我可以给大家介绍一下, 现在微服务,一个大的系统分成几十个甚至几百个服务。一般来说,我们的规定和规范,是要求每个服务只能操作自己对应的一个数据库。
如果你要操作别的服务对应的库,不允许直连别的服务的库,违反微服务架构的规范,你随便交叉胡乱访问,几百个服务的话,全体乱套,这样的一套服务是没法管理的,没法治理的,可能会出现数据被别人改错,自己的库被别人写挂等情况。
如果你要操作别人的服务的库,你必须是通过调用别的服务的接口来实现,绝对不允许交叉访问别人的数据库。

TCC  方案
TCC 的全称是:TryConfirmCancelTry 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留。
Confirm 阶段:这个阶段说的是在各个服务中执行实际的操作。
Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚)
几乎很少人使用,但是也有使用的场景。因为这个事务回滚实际上是严重依赖于你自己写代码来回滚和补偿了,会造成补偿代码巨大。
一般来说跟钱相关的,跟钱打交道的,支付、交易相关的场景,会用 TCC,严格保证分布式事务要么全部成功,要么全部自动回滚,严格保证资金的正确性,保证在资金上不会出现问题。
而且最好是你的各个业务执行的时间都比较短。

本地消息表
本地消息表是国外的一套思想。
这个大概意思是这样的:
A 系统在自己本地一个事务里操作同时,插入一条数据到消息表;
接着 A 系统将这个消息发送到 MQ 中去;
B 系统接收到消息之后,在一个事务里,往自己本地消息表里插入一条数据,同时执行其他的业务操作,如果这个消息已经被处理过了,那么此时这个事务会回滚,这样保证不会重复处理消息;
B 系统执行成功之后,就会更新自己本地消息表的状态以及 A 系统消息表的状态;
如果 B 系统处理失败了,那么就不会更新消息表状态,那么此时 A 系统会定时扫描自己的消息表,如果有未处理的消息,会再次发送到 MQ 中去,让 B 再次处理;
这个方案保证了最终一致性,哪怕 B 事务失败了,但是 A 会不断重发消息,直到 B 那边成功为止。
这个方案说实话最大的问题就在于严重依赖于数据库的消息表来管理事务啥的,如果是高并发场景咋办呢?咋扩展呢?所以一般确实很少用。

可靠消息最终一致性方案
这个的意思,就是干脆不要用本地的消息表了,直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。
大概的意思就是:
A 系统先发送一个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作别执行了;
如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 mq 发送确认消息,如果失败就告诉 mq 回滚消息;
如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务;
mq 会自动定时轮询所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。
这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。
这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQRabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。

最大努力通知方案
这个方案的大致意思就是:
系统 A 本地事务执行完之后,发送个消息到 MQ;
这里会有个专门消费 MQ 的最大努力通知服务,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口;
要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。

springcloud+springboot面试题
https://blog.csdn.net/qq_38891512/article/details/82083389?utm_medium=distribute.pc_relevant_right.none-task-blog-BlogCommendFromMachineLearnPai2-4.nonecase&depth_1-utm_source=distribute.pc_relevant_right.none-task-blog-BlogCommendFromMachineLearnPai2-4.nonecase

5. 熟练掌握dubbo和 SpringCloud 框架,熟练使用核心组件注册中心 EurekaConsul、负载均衡器 Ribbon、客户端调用工具 Feign、分布式配置中心 Config、服务保护 Hystrix、网关 Zuul GatewayApi 管理 Swagger。
涉及:dubbo,SpringCloud(组件)

dubbo
工作原理
  1、提供者在启动时,向注册中心注册自己提供的服务。
  2、消费者启动时,向注册中心订阅自己所需的服务。
  3、注册中心返回提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  4、消费者,从远程接口列表中,调用远程接口,dubbo会基于负载均衡算法,选一台提供者进行调用,如果调用失败则选择另一台。
  5、消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

SpringCloud(组件)
Spring cloud流应用程序启动器是基于Spring BootSpring集成应用程序,提供与外部系统的集成。Spring cloud Task,一个生命周期短暂的微服务框架,用于快速构建执行有限数据处理的应用程序

springcloud的组件
Eureka注册中心,Feign请求调用,Ribbon负载均衡,Hystrix熔断器,Zuul网关

Eureka心跳机制
首先对Eureka注册中心需要了解的是Eureka各个节点都是平等的,没有ZK中角色的概念, 即使N-1个节点挂掉也不会影响其他节点的正常运行。

默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。

由于Eureka拥有自我保护机制,当其注册表里服务因为网络或其他原因出现故障而关停时,Eureka不会剔除服务注册,而是等待其修复。这是AP的一种实现。 
为了让其有精准的 CP健康检查,可以采取让其剔除不健康节点。

什么是服务熔断?什么是服务降级?
服务直接的调用,比如在高并发情况下出现进程阻塞,导致当前线程不可用,慢慢的全部线程阻塞,导致服务器雪崩。
服务熔断:相当于保险丝,出现某个异常,直接熔断整个服务,而不是一直等到服务超时。通过维护一个自己的线程池,当线程到达阈值的时候就启动服务降级,如果其他请求继续访问就直接返回fallback的默认值。

什么是Ribbon
ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。feign默认集成了ribbon。

什么是feigin
1.feign采用的是基于接口的注解
2.feign整合了ribbon,具有负载均衡的能力
3.整合了Hystrix,具有熔断的能力
使用:
1.添加pom依赖。
2.启动类添加@EnableFeignClients
3.定义一个接口@FeignClient(name=“xxx”)指定调用哪个服务

RibbonFeign的区别
1.Ribbon都是调用其他服务的,但方式不同。
2.启动类注解不同,Ribbon@RibbonClient feign的是@EnableFeignClients
3.服务指定的位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明。
4.调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。Feign需要将调用的方法定义成抽象方法即可。

什么是Spring Cloud Bus
spring cloud bus 将分布式的节点用轻量的消息代理连接起来,它可以用于广播配置文件的更改或者服务直接的通讯,也可用于监控。
如果修改了配置文件,发送一次请求,所有的客户端便会重新读取配置文件。
使用:
1.添加依赖
2.配置rabbimq

springcloud断路器作用
当一个服务调用另一个服务由于网络原因或自身原因出现问题,调用者就会等待被调用者的响应 当更多的服务请求到这些资源导致更多的请求等待,发生连锁效应(雪崩效应)
断路器有完全打开状态:一段时间内 达到一定的次数无法调用 并且多次监测没有恢复的迹象 断路器完全打开 那么下次请求就不会请求到该服务
半开:短时间内 有恢复迹象 断路器会将部分请求发给该服务,正常调用时 断路器关闭
关闭:当服务一直处于正常状态 能正常调用

什么是SpringCloudConfig
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。
使用:
1、添加pom依赖
2、配置文件添加相关配置
3、启动类添加注解@EnableConfigServer

Spring Cloud Gateway
Spring Cloud GatewaySpring Cloud官方推出的第二代网关框架,取代Zuul网关。网关作为流量的,在微服务系统中有着非常作用,网关常见的功能有路由转发、权限校验、限流控制等作用。

使用了一个RouteLocatorBuilder的bean去创建路由,除了创建路由RouteLocatorBuilder可以让你添加各种predicates和filters,predicates断言的意思,顾名思义就是根据具体的请求的规则,由具体的route去处理,filters是各种过滤器,用来对请求做各种判断和修改。

什么是Hystrix
防雪崩利器,具备服务降级,服务熔断,依赖隔离,监控
服务降级:优先核心服务,非核心服务不可用或弱可用。通过HystrixCommand注解指定。
fallbackMethod(回退函数)中具体实现降级逻辑。

ZuulGateway的区别
Zuul:
使用的是阻塞式的,不支持长连接,比如 websockets。
底层是servlet,Zuul处理的是http请求
没有提供异步支持,流控等均由hystrix支持。
依赖包spring-cloud-starter-netflix-zuul。
Gateway:
底层依然是servlet,但使用了webflux,多嵌套了一层框架
依赖spring-boot-starter-webflux和/ spring-cloud-starter-gateway
提供了异步支持,提供了抽象负载均衡,提供了抽象流控,并默认实现了RedisRateLimiter。
二、相同点:
1、底层都是servlet
2、两者均是web网关,处理的是http请求
三、不同点:
1、内部实现:
  gateway对比zuul多依赖了spring-webflux,在spring的支持下,功能更强大,内部实现了限流、负载均衡等,扩展性也更强,但同时也限制了仅适合于Spring Cloud套件
  zuul则可以扩展至其他微服务框架中,其内部没有实现限流、负载均衡等。
2、是否支持异步
  zuul仅支持同步
  gateway支持异步。理论上gateway则更适合于提高系统吞吐量(但不一定能有更好的性能),最终性能还需要通过严密的压测来决定
3、框架设计的角度
  gateway具有更好的扩展性,并且其已经发布了2.0.0的RELESE版本,稳定性也是非常好的
4、性能
  WebFlux 模块的名称是 spring-webflux,名称中的 Flux 来源于 Reactor 中的类 FluxSpring webflux 有一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务,在伸缩性方面表现非常好。使用非阻塞API。 Websockets得到支持,并且由于它与Spring紧密集成,所以将会是一个更好的 开发 体验。
  Zuul 1.x,是一个基于阻塞io的API GatewayZuul已经发布了Zuul 2.x,基于Netty,也是非阻塞的,支持长连接,但Spring Cloud暂时还没有整合计划。
使用 Spring Cloud 有什么优势?
使用 Spring Boot 开发分布式微服务时,我们面临以下问题
(1)与分布式系统相关的复杂性-这种开销包括网络问题,延迟开销,带宽问题,安全问题。
(2)服务发现-服务发现工具管理群集中的流程和服务如何查找和互相交谈。它涉及一个服务目录,在该目录中注册服务,然后能够查找并连接到该目录中的服务。
(3)冗余-分布式系统中的冗余问题。
(4)负载平衡 --负载平衡改善跨多个计算资源的工作负荷,诸如计算机,计算机集群,网络链路,中央处理单元,或磁盘驱动器的分布。
(5)性能-问题 由于各种运营开销导致的性能问题。
(6)部署复杂性-Devops 技能的要求。

springcloud和dubbo的区别
1.服务调用方式 dubbo是RPC springcloud Rest Api
2.注册中心,dubbo 是zookeeper springcloud是eureka,也可以是zookeeper
3.服务网关,dubbo本身没有实现,只能通过其他第三方技术整合,springcloud有Zuul路由网关,作为路由服务器,进行消费者的请求分发,springcloud支持断路器,与git完美集成配置文件支持版本控制,事物总线实现配置文件的更新与服务自动装配等等一系列的微服务架构要素。

6. 熟练掌握 OracleMysql 关系型数据库的使用和特性, 熟悉 SQL 语句的编写,sql优化以及分库分表,读写分离。
涉及:OracleMysql ,sql优化,读写分离

Mysql
引擎
MySQL 的存储引擎种类很多,有InnoDBMyISAMMemoryArchive、CSV、Merge、NDB等等

四种常用引擎

InnoDB存储引擎
InnoDB是事务型数据库的首选引擎,支持事务安全表(ACID) ,其它存储引擎都是非事务安全表,支持行锁定和外键, MysQL5.5以后默认使用InnoDB存储引擎。InnoDB特点:支持事务处理,支持外键,支持崩溃修复能力和并发控制。如果需要对事务的完整性要求比较高(比如银行) ,要求实现并发控制(比如售票) ,那选择InnoDB有很大的优势。如果需要频繁的更新、删除操作的数据库,也可以选择InnoDB,因为支持事务的提交(commit)和回滚(rollback)

MylISAM存储引擎
MylISAM拥有较高的插入、查询速度,但不支持事务,不支持外键。web、数据仓储和其他应用环境下最常使用的存储引擎之一。MyISAM特点:插入数据快,空间和内存使用比较低。如果表主要是用于插入新记录和读出记录,那么选择MyISAM能实现处理高效率。如果应用的完整性、并发性要求比较低,也可以使用

MEMORY存储引擎
MEMORY存储引擎将表中的数据存储到内存中,为查询和引用其他表数据提供快速访问。MEMORY特点:所有的数据都在内存中,数据的处理速度快,但是安全性不高。如果需要很快的读写速度,对数据的安全性要求较低,可以选择MEMOEY

Archive存储引擎
如果只有INSERT和SELECT操作,可以选择ArchiveArchive支持高并发的插入操作,但是本身不是事务安全的。Archive非常适合存储归档数据,如记录日志信息可以使用Archiv

索引
单列索引:一个索引只包含单个列,但一个表中可以有多个单列索引。 这里不要搞混淆了。
普通索引:MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值,纯粹为了查询数据更快一点。
唯一索引:索引列中的值必须是唯一的,但是允许为空值,
主键索引:是一种特殊的唯一索引,不允许有空值。
组合索引:在表中的多个字段组合上创建的索引,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用,使用组合索引时遵循最左前缀集合。这个如果还不明白,等后面举例讲解时在细说 
全文索引:只有在MyISAM引擎上才能使用,只能在CHAR,VARCHAR,TEXT类型字段上使用全文索引
空间索引:空间索引是对空间数据类型的字段建立的索引
mysql锁
MySQL有三种锁的粒度:页级、表级、行级。
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
总结:
按照使用方式:
 乐观锁:
在即将修改的时候临时判断一下,比如商品秒杀修改库存时先判断库存是否大于0
悲观锁:
 sql语句后面加上for update;
按照锁的级别:
 共享锁:读锁
SELECT * FROM entrepot WHERE id=1 LOCK IN SHARE MODE ;
排它锁:写锁
select …for update
意向锁
间隙锁
锁的粒度:
 行级锁:
UPDATE entrepot SET price = price+500 WHERE name="aa";  
 	where条件加上索引  为行级锁
 表级锁:
  	 UPDATE entrepot SET price = price+500 WHERE name="aa";
  	where条件不加索引  为表级锁
 页面锁:
  	开销和加锁时间界于表锁和行锁之间  ;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般(不常用)

读写分离
在大量的数据请求下,单台数据库将无法承担所有读写操作。解决方法是配置多台数据库服务器以实现主从复制+读写分离。
优点
增加冗余,增加了机器的处理能力,对于读操作为主的应用,使用读写分离是最好的场景,因为可以确保写的服务器压力更小,而读又可以接受点时间上的延迟。

sql优化
1.加上索引,加上索引考虑哪些情况是索引失效
  导致索引失效的情况
使用  select * 全表扫描
      使用like   '%%' 双百分号会造成全表扫描
     使用like  并且使用or 关键字 如果 两边都是 ‘%%’ 也会造成全表扫描
     使用or关键字 如果有一列未加索引索引失效
   	组合索引(未满足匹配最左前缀)索引失效
2.建立冗余字段 减少表连接操作(基于单表)读快
3.满足数据库三大范式的其中两大范式
第一范式:表中的字段保持原子性:每个字段只做一件事情
第二范式:数据库每个字段必须和主键有直接关系
第三范式:不要有冗余字段
4.使用临时表(把需要多表需要查询的字段合到一张临时表中,单表查询)适用于做报表
5.数据库读写分离
6.优化子查询 在MySQL,尽量使用JOIN来代替子查询.因为子查询需要嵌套查询,嵌套查询时会建立一张临时表,临时表的建立和删除都会有较大的系统开销,而连接查询不会创建临时表,因此效率比嵌套子查询高.
7.对于字段较多的表,如果某些字段使用频率较低,此时应当,将其分离出来从而形成新的表

乐观锁


悲观锁

Java、mysql、redis中的乐观锁和悲观锁
Java乐观锁和悲观锁
Java悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。JavasynchronizedReentrantLock等独占锁就是悲观锁思想的实现。
Java乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
mysql乐观锁和悲观锁
mysql乐观锁
在即将修改的时候临时判断一下,比如商品秒杀修改库存时先判断库存是否大于0
mysql悲观锁
sql语句后面加上for update
redis乐观锁和悲观锁
redis乐观锁 
开启事务前,设置对数据的监听(watch),EXEC时,如果发生数据发生过修改,作用于改数据的事务会自动取消(DISCARD),事务EXEC后,无论成败,监听会被移除                                         
redis悲观锁         
每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁。

oracle
Oracle是一个面向Internet计算环境的数据库。它的系统可移植性好、使用方便、功能强,适用于各类大、中、小、微机环境。它是一种高效率、可靠性好的 适应高吞吐量的数据库解决方案。

7. 熟练使用 MongoDBRedis 等非关系型数据库, 对于 Redis 的常用操作命令、Redis 的 RDB 和 AOF 持久化、Redis 集群主从复制、Redis 分布式锁, Reids 做幂等性等常用技术均有了解。
涉及:MongoDBRedisRedis集群主从复制,redis分布式锁,Redis集群主从复制,Reids 做幂等性

MongoDB
mongodb是一个由C++语言编写的基于分布式文件存储的数据库。
索引
mongodb采用ensureIndex来创建索引


Redis
redis数据类型以及应用场景
        string:验证码
        list:订单列表
        hash:购物车
        set:订单列表
        Sorted Set:排行榜                  
  持久化方式
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录
内存淘汰策略
 过期策略
redis每隔100ms随机抽取一些设置了过期时间的key来检查是否过期。过期了就删除,定期删除可能会导致很多过期key到了时间并没有被删除掉,所以就得靠惰性删除了。
惰性删除
就是获取设置了过期时间的key的时候,redis会检查一下 ,这个key如果过期了,就会删除
这样可能会导致大量的过期key在内存中堆积,解决方法:
内存淘汰机制
  如果redis的内存占用过多的时候,此时会进行内存淘汰,有如下一些策略:
  noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧
  allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
  allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,这个一般没人用吧
  volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key(这个一般不太合适)
  volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
  volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间
缓存穿透,缓存击穿,缓存雪崩
缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用	  ,户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
缓存击穿:key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
解决方式:使用setnx,配合双重锁的机制
redis 应用场景
登录锁定
验证码生成
springSecurity token
分布式事务
限流令牌桶
可以利用缓存池来做MQ
redis的全量复制和增量复制
全量复制:用于首次复制或者其他不能进行部分复制的情况。全量复制是一个非常重的操作,一般我们都要规避它
部分复制:用于从节点短暂中断的情况(网络中断、短暂的服务宕机)。部分复制是一个非常轻量级的操作,因为它只需要将中断期间的命令同步给从节点即可,相比于全量复制,它显得更加高效。
redis多路复用io的原理
redis基于reactor开发了自己的网路事件处理器,被称为文件事件处理器。使用io多路复用来同时监听多个套接字,来响应客户端的连接应答、命令请求、命令恢复。多路复用技术使得redis可以使用单进程单线程运行,逻辑简单。
redis单线程为什么快
完全基于内存
数据结构简单,对数据操作也简单
使用多路 I/O 复用模型
C语言编写,接近于汇编的器语言,运行快速
redis的
“1、热点数据的缓存。2、限时业务的运用,利用这一特性可以运用在限时的优惠活动信息、手机验证码等业务场景。3、计数器相关问题,所以可以运用于高并发的秒杀活动、分布式序列号的生成。4、排行榜相关问题,进行热点数据的排序。 5、分布式锁。”
Redis高并发快总结 
1. Redis是纯内存数据库,一般都是简单的存取操作,线程占用的时间很多,时间的花费主要集中在IO上,所以读取速度快。
2. 再说一下IO,Redis使用的是非阻塞IO,IO多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。
3. Redis采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。
4. 另外,数据结构也帮了不少忙,Redis全程使用hash结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。
5. 还有一点,Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。
redis热点数据收集
在配置文件redis.conf里,有redis的数据淘汰策略的参数,比如按过期时间淘汰,按最低活跃淘汰,可以选择一种策略放行,就是把他前面的#放开,就可以保证数据的及时清理,留下的就是热点数据

redis的常用命令
keys * 查询所有的键
dbsize 查询键总数
exists key 存在返回1,不存在返回0
del key [key...] 返回结果为成功删除键的个数
设置key过期时间
expire key seconds 当超过过期时间,会自动删除,key在seconds秒后过期
expireat key timestamp 键在秒级时间戳timestamp后过期
pexpire key milliseconds 当超过过期时间,会自动删除,key在milliseconds毫秒后过期
pexpireat key milliseconds-timestamp key在豪秒级时间戳timestamp后过期
type key 如果键hello是字符串类型,则返回string;如果键不存在,则返回none
renamenx key newkey 只有newkey不存在时才会被覆盖
randomkey随机返回一个键
select dbIndex切换数据库,默认16个数据库:0-15,进入redis后默认是0库。不建议使用多个数据库
flushdb / flushall用于清除数据库,flushdb只清除当前数据库,flushall清除所有数据库。

redis分布式锁
实现分布式锁要满足3点:多进程可见,互斥,可重入。
方式
利用setnx和expire命令实现加锁。
利用setnx命令加锁
对锁设置了过期时间

Redis集群主从复制
1.Master可以拥有多个slave
2.多个slave可以连接同一个Master外,还可以连接到其他的slave
3.主从复制不会阻塞Master,在主从复制时,Master可以处理client请求。
4.提供系统的伸缩性。 

Reids哨兵机制
在我们基数设备上,装上这个哨兵插件,如果有一台主设备掉线了,这时候哨兵就会开始投票,也可以投他本身,这样被投最多的就成为主设备,然后主设备就上线。

Reids 做幂等性
幂等性
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用
实现方式
唯一索引,防止新增脏数据
token机制,防止页面重复提交
悲观锁,获取数据的时候加锁(锁表或锁行)
乐观锁,基于版本号version实现, 在更新数据那一刻校验数据
分布式锁, redis(jedis、redisson)或zookeeper实现
状态机,状态变更, 更新数据时判断状态

8. 熟练使用RabbitMqKafka等消息队列, 使用 RabbitMQ 解决分布式事务数据一致性问题。
涉及:RabbitMqKafka

RabbitMq
RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。
AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放
标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不
受产品、开发语言等条件的限制
应用场景+;
作用:
1.解耦,系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入,系统A还需要修改代码,过于麻烦!
2.异步,将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度
3.削峰,并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常
RabbitMQ的六种工作模式
1 simple简单模式
2 work工作模式
3 publish/subscribe发布订阅
4 routing路由模式
5 topic 主题模式
6 RPC模式
RabbitMQ的角色
RabbitMQ Server: 
也叫broker server,它是一种传输服务。 他的角色就是维护一条
从ProducerConsumer的路线,保证数据能够按照指定的方式进行传输。
Producer: 
消息生产者,如图ABC,数据的发送方。消息生产者连接RabbitMQ服
务器然后将消息投递到ExchangeConsumer:
消息消费者,如图123,数据的接收方。消息消费者订阅队列,
RabbitMQQueue中的消息发送到消息消费者。
Exchange:
生产者将消息发送到Exchange(交换器),由Exchange将消息路由到一个
或多个Queue中(或者丢弃)。Exchange并不存储消息。RabbitMQ中的Exchange有
direct、fanout、topic、headers四种类型,每种类型对应不同的路由规则。
Queue:
(队列)是RabbitMQ的内部对象,用于存储消息。消息消费者就是通过订阅
队列来获取消息的,RabbitMQ中的消息都只能存储在Queue中,生产者生产消息并最终
投递到Queue中,消费者可以从Queue中获取消息并消费。多个消费者可以订阅同一个
Queue,这时Queue中的消息会被平均分摊给多个消费者进行处理,而不是每个消费者
都收到所有的消息并处理。
RoutingKey:
生产者在将消息发送给Exchange的时候,一般会指定一个routing key,
来指定这个消息的路由规则,而这个routing key需要与Exchange Type及binding key联
合使用才能最终生效。在Exchange Type与binding key固定的情况下(在正常使用时一
般这些内容都是固定配置好的),我们的生产者就可以在发送消息给Exchange时,通过
指定routing key来决定消息流向哪里。RabbitMQ为routing key设定的长度限制为255
bytes。
如何解决丢数据的问题?
生产者丢数据
生产者的消息没有投递到MQ中怎么办?从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息。
transaction机制就是说,发送消息前,开启事物(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事物就会回滚(channel.txRollback()),如果发送成功则提交事物(channel.txCommit())。
消息队列丢数据
处理消息队列丢数据的情况,一般是开启持久化磁盘的配置。这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。
消费者丢数据
启用手动确认模式可以解决这个问题
①自动确认模式,消费者挂掉,待ack的消息回归到队列中。消费者抛出异常,消息会不断的被重发,直到处理成功。不会丢失消息,即便服务挂掉,没有处理完成的消息会重回队列,但是异常会让消息不断重试。
②手动确认模式,如果消费者来不及处理就死掉时,没有响应ack时会重复发送一条信息给其他消费者;如果监听程序处理异常了,且未对异常进行捕获,会一直重复接收消息,然后一直抛异常;如果对异常进行了捕获,但是没有在finally里ack,也会一直重复发送消息(重试机制)。
③不确认模式,acknowledge="none" 不使用确认机制,只要消息发送完成会立即在队列移除,无论客户端异常还是断开,只要发送完就移除,不会重发。
死信队列和延迟队列的使用
死信消息:
消息被拒绝(Basic.RejectBasic.Nack)并且设置 requeue 参数的值为 false
消息过期了
队列达到最大的长度

Kafka
Kafka是分布式发布-订阅消息系统,是一个分布式,可划分的,冗余备份的持久性的日志服务,它主要用于处理流式数据。
kafka的好处
缓冲和削峰:上游数据时有突发流量,下游可能扛不住,或者下游没有足够多的机器来保证冗余,kafka在中间可以起到一个缓冲的作用,把消息暂存在kafka中,下游服务就可以按照自己的节奏进行慢慢处理。
解耦和扩展性:项目开始的时候,并不能确定具体需求。消息队列可以作为一个接口层,解耦重要的业务流程。只需要遵守约定,针对数据编程即可获取扩展能力。
冗余:可以采用一对多的方式,一个生产者发布消息,可以被多个订阅topic的服务消费到,供多个毫无关联的业务使用。
健壮性:消息队列可以堆积请求,所以消费端业务即使短时间死掉,也不会影响主要业务的正常进行。
异步通信:很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
kafka 为什么那么快
Cache Filesystem Cache PageCache缓存
顺序写 由于现代的操作系统提供了预读和写技术,磁盘的顺序写大多数情况下比随机写内存还要快。
Zero-copy 零拷技术减少拷贝次数
Batching of Messages 批量量处理。合并小的请求,然后以流的方式进行交互,直顶网络上限。
Pull 拉模式 使用拉模式进行消息的获取消费,与消费端处理能力相符

9.熟悉掌握 Eclipse/IDEA,GitMaven 等开发及版本控制工具。
涉及:Eclipse/IDEA,GitMaven

Eclipse/IDEA
IDEA比Eclipse更敏锐,因为IDEA懂得上下文

Git
Git是一个分布式的版本控制工具
常用命令
add 		添加
rm 		删除
commit	提交
pull		拉取
push	提交

Git如果发生了版本冲突,你们怎么处理的
我是这么处理的,我会显然我的同事去把他的版本提交到git,然后我再荡下来,然后再合并一下,再提交上去,这样版本冲突的问题就解决了

Maven
Maven 主要服务于基于 Java 平台的项目构建、依赖管理和项目信息管理。
生命周期
1.清理
2.编译
3.测试
4.打包
5.部署
主要功能
依赖管理系统
多模块构建
一致的项目结构
一致的构建模型和插件机制
Maven 常用命令
compile :编译源代码。
deploy :发布项目。
test-compile :编译测试源代码。
test :运行应用程序中的单元测试。
site :生成项目相关信息的网站。
clean :清除项目目录中的生成结果。
package :根据项目生成的 jar/war 等。+
install :在本地 Repository 中安装 jar 。
eclipse:eclipse :生成 Eclipse 项目文件。
jetty:run 启动 Jetty 服务。
tomcat:run :启动 Tomcat 服务。
clean package:清除以前的包后重新打包,跳过测试类。


10.熟悉 Linux 操作系统常用命令及基本的服务器配置。
涉及:Linux操作系统常用命令

Linux环境
我们用的是CentOS6,所以没有docker,docker只是自己没事的时候研究了一下,它可以搭建mysql主从

Linux操作系统常用命令
ls查看列表0·
ll查看列表
cd 切换到当前目录
pwd 查看当前工作目录路径
mkdir 创建文件夹
rm 删除
mv 移动或修改
cp 复制
cat 查看
tail 查看文件末尾
find 查找
chmod 改编权限
ps 查看进程 参数-ef和-aux区别:格式不一样
kill 杀死进程


Nginx
正向代理
我们的客户端在进行操作的时候,我们使用的正是正向代理,通过正向代理的方式,在我们的客户端运行一个软件,将我们的HTTP请求转发到其他不同的服务器端,实现请求的分发。
反向代理
反向代理服务器作用在服务器端,它在服务器端接收客户端的请求,然后将请求分发给具体的服务器进行处理,然后再将服务器的相应结果反馈给客户端。Nginx就是一个反向代理服务器软件。



Docker
Docker是一个容器化平台,它以容器的形式将您的应用程序及其所有依赖项打包在一起,以确保您的应用程序在任何环境中无缝运行。

Docker与虚拟机有何不同
Docker不是虚拟化方法。它依赖于实际实现基于容器的虚拟化或操作系统级虚拟化的其他工具。为此,Docker最初使用LXC驱动程序,然后移动到libcontainer现在重命名为runc。Docker主要专注于在应用程序容器内自动部署应用程序。应用程序容器旨在打包和运行单个服务,而系统容器则设计为运行多个进程,如虚拟机。因此,Docker被视为容器化系统上的容器管理或应用程序部署工具。
A 容器不需要引导操作系统内核,因此可以在不到一秒的时间内创建容器。此功能使基于容器的虚拟化比其他虚拟化方法更加独特和可取。
B 由于基于容器的虚拟化为主机增加了很少或没有开销,因此基于容器的虚拟化具有接近本机的性能。
C 对于基于容器的虚拟化,与其他虚拟化不同,不需要其他软件。
D 主机上的所有容器共享主机的调度程序,从而节省了额外资源的需求。
E 与虚拟机映像相比,容器状态(Docker或LXC映像)的大小很小,因此容器映像很容易分发。
F 容器中的资源管理是通过cgroup实现的。Cgroups不允许容器消耗比分配给它们更多的资源。虽然主机的所有资源都在虚拟机中可见,但无法使用。这可以通过在容器和主机上同时运行top或htop来实现。所有环境的输出看起来都很相似。

什么是Docker镜像
Docker镜像是Docker容器的源代码,Docker镜像用于创建容器。使用build命令创建镜像。

什么是Docker容器
Docker容器包括应用程序及其所有依赖项,作为操作系统的独立进程运行。

Docker容器有几种状态
四种状态:运行、已暂停、重新启动、已退出。

DevOps有哪些优势
持续的软件交付
需要修复不太复杂的问题
更快地解决问题

Dockerfile中最常见的指令是什么
FROM:指定基础镜像
LABEL:功能是为镜像指定标签
RUN:运行指定的命令
CMD:容器启动时要运行的命令

Dockerfile中的命令COPY和ADD命令有什么区别
COPY与ADD的区别COPY的SRC只能是本地文件,其他用法一致

docker常用命令
docker pull 拉取或者更新指定镜像
docker push 将镜像推送至远程仓库
docker rm 删除容器
docker rmi 删除镜像
docker images 列出所有镜像
docker ps 列出所有容器

如何在生产中监控Docker
Docker提供docker stats和docker事件等工具来监控生产中的Docker。我们可以使用这些命令获取重要统计数据的报告。
Docker统计数据:当我们使用容器ID调用docker stats时,我们获得容器的CPU,内存使用情况等。它类似于Linux中的top命令。
Docker事件:Docker事件是一个命令,用于查看Docker守护程序中正在进行的活动流。
一些常见的Docker事件是:attach,commit,die,detach,rename,destroy等。我们还可以使用各种选项来限制或过滤我们感兴趣的事件



项目话术
项目一  
百聚商城(电商项目)
项目介绍
https://blog.csdn.net/qq_32332777/article/details/79053076

项目难点
rabbitmq 重复消费问题  消息幂等性
用户发生了网络问题,一直点点点,导致发送了很多请求,然后在我们后台被rabbitmq 消费了多次,这样就重复消费了
解决方案:404716594
使用setnex  或者getSet 来解决
让页面生成的时候生成一个uuid
你需要让生产者发送每条数据的时候,把uuid传过去,做唯一标示,然后你这里消费到了之后,
先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis 。


库存卖超问题
我们在做压力测试的时候,模拟大量的用户发送订单,导致库存经常会卖成负数
解决方案:
为了解决这个问题,当时我们的小组开会讨论了一下,绝对在Mysql层面加上悲观锁
就是在mysql中的库存操作后面加上for update,这样在操作这条数据时,别的用户线程无法操作这条线程,这样就保证了库存绝对不会卖超
但是后来我们放弃了这个做法,因为,这样数据虽然是安全了,但是性能太慢了,并发量越高,性能越差
于是,我们又尝试了乐观锁,发现效果很好,就是给库存的表加了一个类似于版本号的字段,每次修改都+1,在Update的时候,先判断一下他的版本号是否改变,改变了就执行不成功,这样,即保证了他的性能,又保证了库存不会卖超


你怎么设计一个秒杀系统

小网站
设计开发文档https://blog.csdn.net/zonzereal/article/details/76704455
aophttps://blog.csdn.net/jcw321/article/details/100833237

你可能感兴趣的:(面试)