java面试

一、java基础

1.1、Java 8 大基本数据类型有哪些 (基本数据类型)?

(1)整数类型:long、int、short、byte

(2)浮点类型:float、double

(3)字符类型:char

(4)布尔类型:boolean

1.2、引用数据类型有哪些?

类、接口类型、数据类型、枚举类型、注解类型、字符串型

1.3、方法重载和方法重写的区别 ?

(1)重载发生在同一个类中,方法名相同、参数列表、返回类型、权限修饰符可以不同

(2)重写发生在子类中,方法名、参数列表、返回类型都相同,权限修饰符要大于父类方法,声明异常范围要小于父类方法,但是final和private修饰的方法不可重写

1.4、接口和抽象类的区别?

相似点:

(1)接口和抽象类都不能被实例化

(2)实现接口或继承抽象类的普通子类都必须实现这些抽象方法

不同点:

(1)抽象类可以包含普通方法和代码块,接口里只能包含抽象方法,静态方法和默认方法,

(2)抽象类可以有构造方法,而接口没有

(3)抽象类中的成员变量可以是各种类型的,接口的成员变量只能是 public static final 类型的,并且必须赋值

1.5、==和equals的区别?

==

(1)如果是基本数据类型 , == 比较的是变量的值

(2)如果是引用数据类型 , == 比较的是两个对象的内存地址

equals

(1)如果equals 没有重写的话 ,比较也是两个对象的内存地址

(2)如果重写了equals方法,比较的是两个对象中的属性内容

1.6、Java中的传递 , 都是值传递吗 ?

是的。

(1)基本数据类型传递的是值的副本。

(2)引用数据类型传递的是地址的副本,副本地址和原件地址都指向同一对象。可以根据地址的副本,改变对象的属性。

1.7、java中异常处理机制?

(1)使用try、catch、finaly捕获异常,finaly中的代码一定会执行,捕获异常后程序会继续执行。

(2)使用throws声明该方法可能会抛出的异常类型,出现异常后,程序终止。

1.8、HashMap原理?

(1)HashMap在jdk1.7中采用数组加链表实现,HashMap在Jdk1.8以后是基于数组+链表+红黑树来实现的,提高了插入和整体的查询效率,特点是,key不能重复,可以为null,线程不安全

(2)1.7中链表插⼊使⽤的是头插法,1.8中链表插⼊使⽤的是尾插法,因为1.8中插⼊key和value时需要判断链表元素个数,所以需要遍历链表统计链表元素个数,所以正好就直接使⽤尾插法

(3)HashMap的默认容量为16,默认的负载因子为0.75,当HashMap中元素个数超过容量乘以负载因子的个数时,就创建一个大小为前一次两倍的新数组,再将原来数组中的数据复制到新数组中。当数组长度到达64且链表长度大于8时,链表转为红黑树

(4)计算key的hash值,然后进行二次hash,根据二次hash结果找到对应的索引位置,如果这个位置有值,先进行equals比较,若结果为true则取代该元素,若结果为false,就使用高低位平移法将节点插入链表

1.9、想要线程安全的HashMap怎么办?

(1)使用ConcurrentHashMap

(2)使用HashTable

(3)使用Collections.synchronizedHashMap()方法

1.10、ConcurrentHashMap是如何保证的线程安全?

使用分段锁,将一个Map分为了16个段,每个段都是一个小的hashmap,每次操作只对其中一个段加锁

1.11、HashTable与HashMap的区别?

(1)HashTable的每个方法都用synchronized修饰,因此是线程安全的,但同时读写效率很低

(2)HashTable的Key不允许为null

(3)HashTable只对key进行一次hash,HashMap进行了两次Hash

(4)HashTable底层使用的数组加链表,HashMap底层使用数组加链表加红黑树

(5)Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。

1.12、ArrayList和LinkedList的区别?

(1)ArratList的底层使用动态数组,默认容量为10,当元素数量到达容量时,生成一个新的数组,大小为前一次的1.5倍,然后将原来的数组copy过来;因为数组在内存中是连续的地址,所以ArrayList查找数据更快,由于扩容机制添加数据效率更低

(2)LinkedList的底层使用链表,在内存中是离散的,没有扩容机制;LinkedList在查找数据时需要从头遍历,所以查找慢,但是添加数据效率更高

1.13、如何保证ArrayList的线程安全?

(1)使用collentions.synchronizedList()方法为ArrayList加锁

(2)使用Vector,Vector底层与Arraylist相同,但是每个方法都由synchronized修饰,速度很慢

1.14、String、StringBuffer、StringBuilder的区别?

(1)String 由 char[] 数组构成,使用了 final 修饰,对 String 进行改变时每次都会新生成一个 String 对象,然后把指针指向新的引用对象。

(2)StringBuffer可变并且线程安全

(3)StringBuiler可变但线程不安全。

操作少量字符数据用 String;单线程操作大量数据用 StringBuilder;多线程操作大量数据用 StringBuffer。

1.15、hashCode和equals

(1)hashCode()和equals()都是Obkect类的方法

(2)hashCode()默认是通过地址来计算hash码,但是可能被重写过用内容来计算hash码

(3)equals()默认通过地址判断两个对象是否相等,但是可能被重写用内容来比较两个对象

(4)所以两个对象相等,他们的hashCode和equals一定相等,但是hashCode相等的两个对象未必相等,如果重写equals()必须重写hashCode()。

1.16、面向对象和面向过程的区别

面向对象有封装、继承、多态性的特性,所以相比面向过程易维护、易复用、易扩展,但是因为类调用时要实例化,所以开销大性能比面向过程低。

(1)封装:提高代码的复用性

(2)继承:可以理解为对父类的一种拓展

(3)多态:指的是一种类型多种表现形式,

1.17、深拷贝和浅拷贝

(1)浅拷贝:浅拷贝只复制某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存

(2)深拷贝:深拷贝会创造一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对对象。

1.18、多态的作用?

多态的实现要有继承、重写,父类引用指向子类对象。它的好处是可以消除类型之间的耦合关系,增加类的可扩充性和灵活性。

1.19、什么是反射?

(1)反射是通过获取类的class对象,然后动态的获取到这个类的内部结构,动态的去操作类的属性和方法。

(2)一般是在对一个类的操作权限不够并且想要对里面的类属性和方法进行操作的时候使用,获取class对象有三种方法:class.forName(类路径)、类.class、对象的getClass().

1.20、Java创建对象的5种方式?

(1)new关键字

(2)Class.newInstance

(3)Constructor.newInstance

(4)Clone方法

(5)反序列化

1.21、Java中的四大修饰符 ?

(1)public

(2)protected

(3)default

(4)private

1.22、Object的方法有哪些 ?

(1)equals

(2)hashcode  

(3)toString

(4)clone

(5)finalize

(6)wait

(7)notifyAll

1.23、JDK8新特性有哪些 ?

(1)lambda

(2)stream流

(3)optional

1.24、什么是自动拆装箱  int和Integer有什么区别?

(1)装箱:将基本类型转换成包装类对象

(2)拆箱:将包装类对象转换成基本类型的值

1.25、Java中流的种类

字节流和字符流

3533fde9208c43e4804aed2854e18488.png

4e2997428fe040d1a73dd14b54ae6bbe.png

1.26、GET 和POST 的区别 

(1)GET 请求的数据会附在URL 之后(就是把数据放置在 HTTP 协议头中),以?分割URL 和传输数据,参数之间以&相连;POST 把提交的数据则放置在是 HTTP 包的包体中。

(2)GET 方式提交的数据最多只能是 1024 字节,理论上POST 没有限制,可传较大量的数据。

(3)POST 的安全性要比GET 的安全性高

(4)Get 是向服务器发索取数据的一种请求,而 Post 是向服务器提交数据的一种请求,在 FORM(表单)中,Method

1.27、Cookie 和Session 的区别

(1)Cookie 是 web 服务器发送给浏览器的一块信息,浏览器会在本地一个文件中给每个 web 服务器存储 cookie。以后浏览器再给特定的 web 服务器发送请求时,同时会发送所有为该服务器存储的 cookie

(2)Session 是存储在 web 服务器端的一块信息。session 对象存储特定用户会话所需的属性及配置信息。当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去

1.28、Cookie 和session 的不同点

(1)安全性: Session 比 Cookie 安全,Session 是存储在服务器端的,Cookie 是存储在客户端的。
(2)存取值的类型不同:Cookie 只支持存字符串数据,Session 可以存任意数据类型。
(3)有效期不同: Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。
(4)存储大小不同: 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。

1.29、什么是http协议 ?

http协议是一个简单的请求、响应协议,它通常运行在TCP之上,也就是说传输层用的是TCP协议。它指定了客户端可能发送给服务器什么样的数据,以及得到什么样的响应

1.30、什么是TCP/IP协议 ?

TCP/IP协议群指的是利用IP通信时所用到的协议的统称,比如应用层协议有http , smtp ,传输层协议有TCP,UDP,网络层有IP等,这些协议的统称就叫TCP/IP协议群。

1.31、Servlet的生命周期是怎样的 ?

 (1)init() :初始化属性。首先创建了对象,再来调用init方法

(2)service() : 服务,每次访问都会调用

(3)destory() : 方法并不是销毁Servlet对象,而是释放Servlet的一些资源,当服务器关闭时会调用

1.32、JSP 9大内置对象 ?

(1)request请求对象

(2)response响应对象

(3)session会话对象

(4)application应用程序对象

(5)out输出对象

(6)config配置对象

(7)pageContext页面上下文对象

(8)page页面对象

(9)exception页面上下文对象

1.33、请求和重定向的区别?

(1)请求转发对客户端浏览器而言是在一次请求和一次响应中进行的,而重定向是在两次请求和两次响应中

(2)请求转发并不会改变浏览器在地址栏中的URL。而重定向会改变浏览器地址栏中的URL

(3)请求转发可以使用request对象传递数据,而重定向不能使用request对象传递数据。

1.34、什么是跨域请求 ?

在javaScript的请求中当一个请求URL的协议、域名、端口三者之中任意一个与当前页面的访问URL不同时,即为跨域。浏览器执行javaScript脚本时,会检查当前请求是否同源,如果不是同源中的资源,就不会执行

1.35、final、finally、finalize

(1)final修饰的对象,不可被改变,final修饰的类不能被继承,final修饰的方法不能被重写

(2)finally一般搭配try一起使用,表示无论是否出现异常,都会执行finally里中的内容。

(3)finalize表示主动告知GC机制回收对象,但是并不代表一定立马回收

1.36、JDK、JRE、JVM之间的区别

(1)JDK(开发工具包):Java标准开发包,它提供了编译、运⾏Java程序所需的各种⼯具和资源,包括Java编译器、Java运⾏时环境,以及常⽤的Java类库等

(2)JRE(java运行环境):Java运⾏环境,⽤于运⾏Java的字节码⽂件。JRE中包括了JVM以及JVM⼯作所需要的类库,普通⽤户⽽只需要安装JRE来运⾏Java程序,⽽程序开发者必须安装JDK来编译、调试程序。

(3)JVM(Java虚拟机):Java虚拟机,是JRE的⼀部分,它是整个java实现跨平台的最核⼼的部分,负责运⾏字节码⽂件。

JDK包含JRE包含JVM

1.37、泛型中extends和super的区别

(1)<? extends T>表示包括T在内的任何T的⼦类

(2)<? super T>表示包括T在内的任何T的⽗类

1.38、List和Set的区别

(1)list方法可以允许重复的对象,而set方法不允许重复对象

(2)list可以插入多个null元素,而set只允许插入一个null元素

(3)list是一个有序的容器,保持了每个元素的插入顺序。即输出顺序就是输入顺序,而set方法是无序容器,无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序

1.39、throw和throws的区别是什么?

(1)throw是语句抛出一个异常,throws是方法可能抛出异常的声明

(2)throws出现在方法函数头,而throw出现在函数体

(3)throw是指抛出一个异常的动作,而throws代表一种状态,指的是可能有异常抛出

(4)throw只能用于抛出一种异常,而throws可以抛出多个异常

二、java多线程

2.1、创建线程的方式?

(1)继承 Tread 类

(2)实现 Runnable 接口

(3)实现 Callable 接口:带有返回值

(4)线程池创建线程

2.2、线程和进程的区别,进程之间是如何通信的?

(1)进程:系统运行的基本单位,进程在运行过程中都是相互独立,但是线程之间运行可以相互影响。

(2)线程:独立运行的最小单位,一个进程包含多个线程并且共享一个进程中的系统资源。

(3)通信:进程之间是通过管道、共享内存、信号量机制、消息队列进行通讯。

2.3、Main方法启动时,至少有哪些线程 ?

(1)主线程

(2)处理引用线程

(3)处理垃圾回收线程

(4)处理分发给jvm命令的线程

(5)接受外部命令线程

2.4、线程有哪些状态?

(1)新建状态:线程对象被创建后,就进入了新建状态

(2)就绪状态:也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程

(3)运行状态:线程获取CPU权限进行执行

(4)阻塞状态:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行

(5)阻塞的情况分三种:

                1、等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。

                2、同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。

                3、其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

(6)销毁状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期

2.5、什么是上下文切换?

当一个线程被剥夺CPU执行权,切换到另一个线程的时候

2.6、什么是死锁?

死锁就是多个线程在执行任务的过程中,为了争夺资源而造成相互等待的僵局。

2.7、创造死锁的必要条件?

(1)互斥锁:同一个资源同时只能有一个线程执行

(2)抢占条件:不能强行剥夺占有资源

(3)请求和保持条件:在请求资源的同时,对自己手中的资源保持不放

(4)循环依赖条件:在等待资源的过程中,形成一个闭环

(5)解决死锁的方法:可以打破其中一个条件即可,比如使用定时锁;尽量让线程使用相同的枷锁顺序;用银行家算法可以预防死锁

2.8、Synchrpnized和lock的区别

(1)Synchrpnized是Java关键字,Lock是一个类

(2)Synchrpnized在发生异常是自动释放锁,而Lock需要手动释放锁

(3)Synchrpnized是可重入锁、不可中断锁、非公平锁。Lock是可重入锁、可中断锁、即是非公平也是公平锁。

(4)Synchrpnized是jvm层次采用监视器实现的,而Lock是AQS实现的

2.9、sleep()和wait()的区别?

(1)wait()是Object的方法,sleep()是Thread的方法

(2)wait()需要在同步方法中或者代码快中执行,而sleep()没有限制。

(3)wait()会释放锁,sleep()不会释放锁。

(4)wait()要调用notify()或notifyall()唤醒,sleep()自动唤醒。

2.10、yield()和join()区别?

(1)yield()调用后进入就绪状态

(2)A线程中调用B线程的join(),则B执行完前A进入阻塞状态

2.11、线程池的七大核心参数?

(1)核心线程数

(2)最大线程数

(3)线程等待死亡时间

(4)线程等待死亡时间单位

(5)阻塞队列

(6)线程工厂

(7)拒绝策略

2.12、保证并发的三大特性?

原子性:一次或多次操作执行期间不被其他线程影响

保证并发的原子性:

(1)使用Synchronized来修饰某个方法或者代码块,当线程来访问这段代码的时候必须先要拿到该锁才可以去执行,这样的话我们就做到了完全的互斥,当线程A执行的时候线程B要等待A执行完毕释放所,B才能去执行

(2)使用CAS,而这个CAS是java中一种乐观锁的实现,而Synchronized是一种悲观锁,如果线程没拿到锁的资源时候会挂起,这个时候就涉及到用户态和内核态的一个切换,就会造成资源的一个消耗。而CAS它是操作系统或者是CPU层面的一个并发语言,它是在CPU层面保证原子性,A线程在执行的时候B线程是不可以执行的

(3)使用Lock锁,对一段代码块进行加锁和释放锁,避免多个线程同时操作统一资源

(4)使用ThreadLocal,虽然使用ThreadLocal很难保证原子性,一般我们使其原子性就是为了避免多个线程去操作变量从而带来线程安全问题,而ThreadLocal在一定层面上是不让多线程去操作共享资源,让每个线程去操作属于自己的数据

可见性:当一个线程对一个元素进行修改时,其他线程立马就知道

保证并发的可见性:

(1)使用volatile关键字修饰,用来修饰成员变量,就是让CPU每次去操作时,无论是写还是读,写的话强制写到主内存,读的话强制从主内存中去做到一个读操作,那么就可以在CPU层面解决数据不一致的问题

(2)使用Synchronized锁,在加锁的同时可以将数据同步到主内存中,但是它只是在加锁的这一时刻它会同步数据,如果在内部做一些额外操作,那它依然是无法保证可见性的

(3)使用Lock锁,它是基于CAS去操作volatile修饰变量,但是它和Synchronized类似,也只是在执行的那一时刻保证可见性

(3)使用final修饰,被final修饰过后是不允许修改的,自然就保证了数据的可见性

有序性:jvm对指令的优化会让执行顺序发生改变,有序性是禁止指令重排

保证并发的有序性:

(1)使用volatile关键字,会在某个指令的前后加上不同的内存屏障来保证有序性,禁止指令重排

2.13、ThreadLocal原理

(1)原理就是为每个线程创建一个变量副本,线程与线程之间相互不可见,保证了线程的安全,每个线程内部都维护了一个Map,key为存放的是ThreadLocal实例化,value存放的是变量副本。

(2)使用ThreadLocal会存在内存泄漏问题,因为key是弱引用,而value是强引用,每次GC垃圾回收是key都会被回收,而value不会被回收,为了解决内存泄漏问题,我们可以在每次使用完ThreadLocal后调用remove方法删除value或者使用static修饰ThreadLocal,这样随时都可以获取到value。

2.14、什么是CAS锁?

CAS锁可以保证原子性,思想是更新内存时会判断内存值是否被别人修改过,如果没有就直接更新。如果被修改,就重新获取值,直到更新完成为止。这样的缺点是:

(1)只能支持一个变量的原子操作,不能保证整个代码块的原子操作

(2)CAS频繁失败导致CPU开销大

(3)ABS问题:线程1和线程2同时去修改一个变量,将值从A改为B,但线程1突然阻塞,此时线程2将A改为B,然后线程3又将B改成A,此时线程1将A又改为B,这个过程线程2是不知道的,这就是ABA问题,可以通过版本号或时间戳解决

2.15、Synchronized锁原理和优化

Synchronized是通过对对象头markword来表明监视的,监视器本质就是依赖了操作系统的互斥锁实现的,操作系统实现线程的切换需要从用户态切换成核心态,这种操作是很耗性能,所以这种锁就叫做重量级锁,所以为了解决这一问题,随后引入了偏向锁、轻量级锁、重量级锁。

(1)偏向锁:当一个代码没有其他线程来访问时,一个线程来访问就获取到偏向锁

(2)轻量级锁:当为偏向锁的时候,另一个线程来访问的时候,则升级为轻量级锁

(3)重量级锁:当为轻量级锁的时候,轻量级锁自旋一段时间后仍未获取到锁,这时就获取到重量级锁,来竞争的所有线程都会堵塞,性能降低。

注意的是锁只能升级不能降级

2.16、线程池的作用,为什么要用线程池?

(1)节省系统资源:线程的频繁创建和销毁消耗性能

(2)提高处理时间:线程创建很消耗时间

(3)便于管理:只需要配置配置核心线程数, 最大线程数, 队列, 拒绝策略等

2.17、线程池的几种状态?

(1)运行(Running)

(2)停机(ShutDown)

(3)停止(Stop)

(4)整理(Tidying)

(5)终止(Terminated)

2.18、线程池的拒绝策略?

(1)AbortPolicy(抛出异常-默认):当线程池队列已满且无法继续接受新任务时,会抛出RejectedExecutionException异常。

(2)DiscardPolicy(丢弃任务不抛出异常):当线程池队列已满且无法继续接受新任务时,会直接丢弃新提交的任务,不做任何处理。

(3)CallerRunsPolicy(退回任务):当线程池队列已满且无法继续接受新任务时,由提交任务的线程自己执行该任务,这样可以保证不丢失任务,但可能会影响提交任务的线程的性能。

(4)DiscardOldestPolicy(尝试和最老的线程竞争):当线程池队列已满且无法继续接受新任务时,会丢弃队列中最老的任务,然后将新任务加入队列。

(5)Custom Policy(自定义策略):开发者可以根据具体的业务需求自定义拒绝策略,例如将被拒绝的任务记录到日志中、放入另一个队列中等。

2.19、线程池的队列类型?

(1)无界队列(Unbounded Queue):队列没有大小限制,可以无限制地向其中添加任务,直到系统资源耗尽。

(2)有界队列(Bounded Queue):队列有一个最大容量限制,一旦达到限制,后续提交的任务会阻塞等待队列中的任务被处理。

(3)优先级队列(Priority Queue):队列中的任务按照优先级顺序进行处理,高优先级的任务先被执行。

(4)同步移交队列(Synchronous Queue):任务提交到队列时,需要等待一个线程来处理它,直到有线程来取走任务,否则提交线程会阻塞。

(5)延迟队列(Delay Queue):队列中的元素只有在延迟期满后才能被取出,常用于任务调度等场景。

2.20、线程池的工作过程和执行流程?

当创建了一个线程任务,并使用线程池执行时。首先任务会进入到线程池的Dispatcher分发器中,然后Dispatcher首先会查看核心线程是否有空闲的,如果有则交给核心线程来处理。如果核心线程数都处于工作状态,则会查看队列是否已达上限,如果队列未达上限,则进入到队列中等待执行。如果队列已达上限,则判断线程池是否已经达到最大线程数,如果未达到最大线程数,则新建一个线程来执行,如果已经达到了最大线程数,则根据拒绝策略处理这个任务,是抛出异常还是直接丢弃。

2.21、线程结束的方式

线程结束的方式有很多,最常用的就是让线程的run方法结束,无论是return结束,还是抛出异常结束,都可以

(1)stop方法:无论线程在执行什么任务,强制让线程结束

public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        Thread.sleep(500);
        thread.stop();
        System.out.println(thread.getState());
    }

(2)使用共享变量

static volatile boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (flag){
                //处理任务
            }
            System.out.println("任务结束!");
        });
        thread.start();
        Thread.sleep(500);
        flag = false;
    }

(3)interrupt方式

public static void main(String[] args) throws InterruptedException {
        // 线程默认情况下,interrupt标记为 false
        System.out.println(Thread.currentThread().isInterrupted());
        // 执行interrupt之后,再次查看打断信息
        Thread.currentThread().interrupt();
        // interrupt标记为 true
        System.out.println(Thread.currentThread().isInterrupted());
        // 返回当前线程,并回归为false interrupt标记为 true
        System.out.println(Thread.interrupted());
        // 已经归位
        System.out.println(Thread.interrupted());
        // ========================================
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                // 业务处理
            }
            System.out.println("任务结束");
        });
        thread.start();
        Thread.sleep(500);
        thread.interrupt();
    }

2.22、java中锁的分类

可重入锁、不可重入锁

Java中提供的synchronized、ReentrantLock、ReentrantReadWriteLock都是可重入锁。

(1)重入:当线程获取到A锁,在获取后尝试再次获取A锁可以直接拿到

(2)不可重入:当线程获取到A锁,在获取后尝试再次获取A锁,无法获取到的,因为A锁被当前线程占用着,需要等到自己释放锁后方可再次获取到。

乐观所、悲观所

Java中提供的synchronized、ReentrantLock、ReentrantReadWriteLock都是悲观锁。

java中提供的CAS操作,就是一种乐观锁的实现

(1)乐观锁:线程获取不到锁资源的时候,会让CPU再次调度,重新尝试获取锁的资源,不会将线程挂起

(2)悲观锁:线程获取不到锁资源的时候,会将当前线程挂起,线程挂起会涉及到用户态和内核态的切换,而这种切换是比较消耗资源的

        ---用户态:JVM可以自行执行的命令,不需要借助操作系统执行

        ---内核态:JVM不可以自行执行,需要借助操作系统执行

公平锁、非公平锁

Java中提供的synchronized只能是非公平锁。

Java中ReentrantLock、ReentrantReadWriteLock既可以实现公平锁也可以实现非公平锁,ReentrantLock是可重入锁,不管是公平还是非公平锁都是可重入。

(1)公平锁:公平锁是一种思想,多个线程按照申请锁的顺序来获取锁。在并发环境中,每个线程会先查看此锁维护的等待队列,如果当前等待队列为空,则占有锁,如果等待队列不为空,则加入到等待队列的末尾,按照FIFO的原则从队列中拿到线程,然后占有锁。

        ---(如:线程A获取到锁,线程B没拿到,线程B去排队,线程C来了,锁被线程A所持有,同时线程B在排队,因此C需要排到B后面,等到B拿到锁或者B取消后,C才可以尝试竞争锁资源)

(2)非公平锁:非公平锁是一种思想,线程尝试获取锁,如果获取不到,则再采用公平锁的方式。多个线程获取锁的顺序,不是按照先到先得的顺序,有可能后申请锁的线程比先申请的线程优先获取锁。

        ---(如:线程A拿到资源锁,线程B没拿到,线程B去排队,线程C来了,先尝试竞争一波,拿到锁资源插队成功,没拿到锁资源,依然要排到B线程后面,等到B拿到锁资源或者取消后,才能尝试竞争锁资源)

互斥锁、共享锁

Java中提供的synchronized、ReentrantLock都是互斥锁。

Java中提供的ReentrantReadWriteLock有互斥锁也有共享锁。

(1)互斥锁:同一时间点,只会有一个线程持有者,其他线程不能访问。

(2)共享锁:同一时间点,当前共享锁可以被多个线程同时持有。

2.23、什么是AQS?

AQS是一个抽象类,可以用来构造锁和同步类,AQS是JUC包下的一个基础类,基于AQS实现的类有很多,如ReentrantLock,ThreadPooExecutor,阻塞队列、Semaphore、CountDownLatch、CyclicBarrier等等都是基于AQS实现的。

AQS的原理是,AQS内部有三个核心组件:

(1)一个是state代表加锁状态初始值为0

(2)一个是获取到锁的线程

(3)还有一个阻塞队列

当有线程想获取锁时,会以CAS的形式将state变为1,CAS成功后便将加锁线程设为自己。当其他线程来竞争锁时会判断state是不是0,不是0再判断加锁线程是不是自己,不是的话就把自己放入阻塞队列。这个阻塞队列是用双向链表实现的

可重入锁的原理就是每次加锁时判断一下加锁线程是不是自己,是的话state+1,释放锁的时候就将state-1。当state减到0的时候就去唤醒阻塞队列的第一个线程。

2.24、为什么AQS使用的双向链表?

因为有一些线程可能发生中断 ,而发生中断时候就需要在同步阻塞队列中删除掉,这个时候用双向链表方便删除掉中间的节点

2.25、有哪些常见的AQS锁

AQS分为独占锁和共享锁:

ReentrantLock(独占锁):可重入,可中断,可以是公平锁也可以是非公平锁,非公平锁就是会通过两次CAS去抢占锁,公平锁会按队列顺序排队

Semaphore(信号量):设定一个信号量,当调用acquire()时判断是否还有信号,有就获取一个信号量,没有就阻塞等待其他线程释放信号量,当调用release()时释放一个信号量,唤醒阻塞线程。

应用场景:允许多个线程访问某个临界资源时,如上下车,买卖票

CountDownLatch(倒计数器):给计数器设置一个初始值,当调用CountDown()时计数器减一,当调用await() 时判断计数器是否归0,不为0就阻塞,直到计数器为0。

应用场景:启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行

CyclicBarrier(循环栅栏):给计数器设置一个目标值,当调用await() 时会计数+1并判断计数器是否达到目标值,未达到就阻塞,直到计数器达到目标值

应用场景:多线程计算数据,最后合并计算结果的应用场景

2.26、有几种线程池?

(1)newFixedThreadPool:线程数是固定的

(2)newSingleThreadExecutor:它是一个单例线程池,线程池中只有工作线程在处理任务

(3)newCachedThreadPool:它是一个缓存的线程池,没有核心工作线程的,但是它可以创建出非核心线程去处理任务

(4)newScheduleThreadPool:它是个定时任务的线程池,而这个线程池就是可以以一定周期去执行一个任务,或者是延迟多久执行一个任务一次

(5)newWorkStealingPool:当前JDK提供构建线程池的方式newWorkStealingPool和之前的线程池很非常大的区别,之前定长,单例,缓存,定时任务都基于ThreadPoolExecutor去实现的。newWorkStealingPool是基于ForkJoinPool构建出来的,它是一个工作窃取的线程池(每个线程对应一个队列,当自己的阻塞队列中的任务执行完后,它会去其他的阻塞队列中获取任务执行,从而提升工作效率)

2.27、线程中 sleep()、wait()、join()、yield()的区别

(1)sleep()主要是为了暂停当前线程,把cpu片段让出给其他线程,减缓当前线程的执行

(2)wait()方法需要和notify()及notifyAll()两个方法一起介绍,这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用,wait()方法与sleep()方法的不同之处在于,wait()方法会释放对象的“锁标志”。当调用某一对象的wait()方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了notify()方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的notifyAll()方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池

(3)yield()方法和sleep()方法类似,也不会释放“锁标志”,yield()方法只会给相同优先级或更高优先级的线程运行的机会

(4)主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块

三、JVM篇

3.1、jvm内存结构

私有区:

(1)虚拟机桟:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧

(2)本地方法:为native修饰的本地方法提供的空间

(3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码

线程共享区:

(1)堆内存:Jvm进行垃圾回收的主要区域,存放对象信息,分为新生代和老年代,内存比例为1:2,新生代的Eden区内存不够时时发生MinorGC,老年代内存不够时发生FullGC

(2)方法区:存放类信息、静态变量、常量、运行时常量池等信息。JDK1.8之前用持久代实现,JDK1.8后用元空间实现,元空间使用的是本地内存,而非在JVM内存结构中

c7e5646ef3924e39a905c2d28732b0ef.png

3.2、什么情况下会内存溢出?

堆内存溢出:

(1)当一直创建对象而不被回收的时候

(2)加载类越来越多的时候

(3)虚拟机桟中的线程越来越多的时候

桟内存溢出:

(1)方法调用次数过多的时候,一般这种情况是递归调用不当造成的

3.3、jvm有哪些垃圾回收机制?

(1)标记清理算法:标记不需要回收的对象,然后清除没有标记的对象,会造成许多内存碎片。

(2)复制算法:将内存分为两块,只使用一块,进行垃圾回收时,先将存活的对象复制到另一块区域,然后清空之前的区域。用在新生代。

(3)标记整理算法:与标记清除算法类似,但是在标记之后,将存活对象向一端移动,然后清除边界外的垃圾对象。用在老年代。

3.4、GC如何判断对象可以被回收?

(1)引用计数法:这种⽅式是给堆内存当中的每个对象记录⼀个引⽤个数。引⽤个数为0的就认为是垃圾。这是早期JDK中使⽤的⽅式。引⽤计数⽆法解决循环引⽤的问题。

(2)可达性分析:这种⽅式是在内存中,从根对象向下⼀直找引⽤,找到的对象就不是垃圾,没找到的对象就是垃圾。

3.5、垃圾回收器有哪些?

(1)CMS:以最小的停顿时间为目标、只运行在老年代的垃圾回收器,使用标记-清除算法,可以并发收集。

(2)G1 :JDK1.9以后的默认垃圾回收器,注重响应速度,支持并发,采用标记整理+复制算法回收内存,使用可达性分析法来判断对象是否可以被回收。

3.6、类的双亲委派机制是什么?

(1)当一个类加载器去加载某个类的时候,会自底向上查找是否加载过,如果加载过就直接返回,如果一直到最顶层的类加载器都没有加载,再由顶向下进行加载。

(2)应用程序类加载器的父类加载器是扩展类加载器,扩展类加载器的父类加载器是启动类加载器。

(3)双亲委派机制的好处有两点:第一是避免恶意代码替换JDK中的核心类库,比如java.lang.String,确保核心类库的完整性和安全性。第二是避免一个类重复地被加载。

3.7、从父类加载器到字类加载器?

(1)BootStrapClassLoader(启动类加载器)    加载路径为:JAVA_HOME/jre/lib

(2)ExtensionClassLoader(拓展类下载器)    加载路径为:JAVA_HOME/jre/lib/ext

(3)ApplicationClassLoader(应用类加载器)  加载路径为:classpath

(4)自定义类加载器

当一个类加载器收到类加载请求时,会先把这个请求交给父类加载器处理,若父类加载器找不到该类,再由自己去寻找。该机制可以避免类被重复加载,还可以避免系统级别的类被篡改

3.8、JVM中有哪些引用?

(1)强引用:创建对象的时候,哪怕内存溢出也不会被回收

(2)软引用:当内存快满了的时候才会被回收

(3)弱引用:每次回收的时候会被回收

(4)虚引用:必须配合引用队列使用,一般用于追踪垃圾回收动作

3.9、类的加载过程?

(1)加载:把字节码通过二进制的方式转化到方法区中的运行数据区

(2)连接:

              --验证:验证字节码文件的正确性。

              --准备:正式为类变量在方法区中分配内存,并设置初始值,final类型的变量在编译时已经赋值了

              --解析:将常量池中的符号引用(如类的全限定名)解析为直接引用(类在实际内存中的地址)

(3)初始化:执行类构造器(不是常规的构造方法),为静态变量赋初值并初始化静态代码块

(4)使用

(5)卸载

00bfef381c5f4da2ae52886d889dfaa8.png

3.10、对象的创建过程?

(1)检查类是否被加载,没有加载就先加载类

(2)为对象在堆内存中分配内存

(3)初始化,讲对象中属性都分配0值或者null

(4)设置对象头

(5)为属性赋值

3.11、对象和对象头重有哪些信息?

对象

(1)对象头:存放的是对象运行时的一些信息

(2)实力数据:就是在初始化对象的时候设定的属性和方法

(3)对齐填充字节:为了满足“java对象满足大小必须是8比特的倍数”这一条件设计的

对象头

(1)一部分存放MarkWork,运行时的数据,如对象的hashcode、GC分代年龄、GC标记、锁状态、获取锁的线程ID等

(2)另一部分存放对象的所属类,如果是数组,还会存放数组长度。

3.12、GC的回收机制和原理?

GC回收机制原理是让内存自动释放,使用可达性分析判断对象是否可以回收,采用分代回收机制思想,将堆内存分为两部分:新生代、老年代,新生代采用复制算法,老年代采用整理算法,当新生代内存不足的时候发生minorGC,当老年代内存不足的时候发生fullGC。

3.13、什么是悲观锁,什么是乐观锁?

(1)悲观锁 :Sychronized、Lock都是独占锁,即悲观锁,会导致其他所有需要锁的线程挂起,等待持有锁的线程释放

(2)乐观锁 :不加锁,而是假设每次没有冲突都能成功完成某项操作,如果因为冲突失败就重试,直到成功为止

3.14、Synchronized加载方法和加载代码块上有什么区别 ?

(1)Synchronized修饰代码块时,编译后的字节码会有monitor enter 和monitor exit 指令,分别对应获得锁和解锁

(2)Synchronized修饰方法时,会给方法加上ACC_SYNCHRONIZED,告知JVM这是一个同步方法,进入方法时需要进行锁的竞争,拿到锁的线程才有资格进入临界区

3.15、⼀个对象从加载到JVM,再到被GC清除,都经历了什么过程?

(1)⾸先把字节码⽂件内容加载到⽅法区

(2)然后再根据类信息在堆区创建对象

(3)对象⾸先会分配在堆区中年轻代的Eden区,经过⼀次Minor GC后,对象如果存活,就会进⼊Suvivor区。在后续的每次Minor GC中,如果对象⼀直存活,就会在Suvivor区来回拷⻉,每移动⼀次,年龄加1

(4)当年龄超过15后,对象依然存活,对象就会进⼊⽼年代

(5)如果经过Full GC,被标记为垃圾对象,那么就会被GC线程清理掉

3.16、对守护线程的理解

线程分为⽤户线程和守护线程,⽤户线程就是普通线程,守护线程就是JVM的后台线程,⽐如垃圾回收线程就是⼀个守护线程,守护线程会在其他普通线程都停⽌运⾏之后⾃动关闭。

3.17、并发、并⾏、串⾏之间的区别

(1)串⾏:⼀个任务执⾏完,才能执⾏下⼀个任务

(2)并⾏(Parallelism):两个任务同时执⾏

(3)并发(Concurrency):两个任务整体看上去是同时执⾏,在底层,两个任务被拆成了很多份,然后⼀个⼀个执⾏,站在更⾼的⻆度看来两个任务是同时在执⾏的

四、MySql篇

4.1、解释一下InnoDB

(1)InnoDB三大特性:事务、外键、行级锁

(2)InnoDB是聚簇索引,不支持全文索引

(3)InnoDB支持自增

4.2、Mysql的特性

(1)原子性:一个事务内要么全部失败要么全部成功

(2)一致性:当一个事务执行后前后数量是不变的

(3)隔离性:事务和事务之间是互不影响的

(4)持久性:事务一旦提交发生改变是不可逆的

4.3、事务靠什么保证

(1)原子性:由undolog日志保证,它记录了要回滚的信息,回滚时撤销已执行的sql

(2)一致性:由其他三大特性共同保证,是事务的目的

(3)隔离性:由MVCC保证

(4)持久性:由redolog日志和内存保证,mysql修改数据时内存和redolog会记录操作,宕机时可恢复

4.4、事务的隔离级别

在高并发情况下,并发事务会产生脏读、不可重复读、幻读问题,这时需要用隔离级别来控制

(1)读已提交:允许一个事务读取另一个事务已提交的数据,可能出现不可重复读,幻读。

(2)读提交:    只允许事务读取另一个事务没有提交的数据可能出现不可重复读,幻读。

(3)可重复读: 确保同一字段多次读取结果一致,可能出现欢幻读。

(4)可串行化: 所有事务逐次执行,没有并发问日

4.5、Mysql有哪些索引?

(1)主键索引:一张表只能有一个主键索引,主键索引列不能有空值和重复值

(2)唯一索引:唯一索引不能有相同值,但允许为空

(3)普通索引:允许出现重复值

(4)组合索引:对多个字段进行联合索引,减少索引开销,遵循最左匹配原则

(5)全文索引:通过倒排索引提升检索效率,广泛用于搜索引擎

4.6、那些情况下索引会失效?

(1)where条件中有or

(2)like查询用%开头

(3)索引列参与计算

(4)违背最左匹配原则

(5)索引字段发生类型转换

(6)mysql觉得全表扫描更快时(数据少),索引失效

4.7、聚合索引和非聚和索引的区别

(1)聚合索引:叶子节点存放的是主键和行数据

(2)非聚会索引:叶子节点存放的是数据行的地址,先根据索引找到数据地址,再根据地址去找到数据

(3)他们都是b+数据结构

4.8、Mysql集群怎么搭建?怎么实现读写分离?

(1)我们可以搭建2个或者2个以上的mysql数据库,将一个设置为主机,主机可以增删改,从机只能读数据。

(2)主机的数据会同步到从机,同步原理当主机DML操作后,会向binlog日志里写SQL语句记录 ,赋予了从机权限后,从机便可以从主机的binlog日志里偷取到SQL语句 ,然后放入从机的readylog日志中,并执行语句。

4.9、什么是数据库连接池,为什么要使用数据库连接池?

数据库连接池是一个容器,一个集合。它通过事先创建一定数量的数据库连接并将其保存在这个容器中,然后应用程序需要与数据库建立连接时,就从连接池中获取一个可用的连接,使用完毕后再归还给连接池,而不是每次使用的时候都需要创建连接和关闭连接。

使用连接池的好处:

(1)资源重用,为了避免重复创建和销毁连接

(2)提高性能

(3)方便管理

(4)限制连接数量

4.10、为什么使用内连接而不是用外连接?

用外连接连接顺序固定死了,比如左连接,它必须先对左表进行全表扫描,然后一条条的去右表进行匹配,而内连接mysql会根据查询优化器去判断用那个表做驱动。尽量使用小表做驱动

4.11、Mysql查询过程

(1)客户端向MySQL服务器发送一条查询请求

(2)Mysql服务器会检查缓存里面有没有数据,如果有就将数据返回给客户端,如果没有就进入下一阶段

(3)服务器进行sql解析、预编译、再由优化器生成执行计划

(4)mysql根据执行计划,调用存储引擎的API来执行查询

(5)将查到的数据返回给客户端,然后同时将数据进行缓存

注意:只有在8.0之前才有查询缓存,8.0之后查询缓存被去掉了

4.12、MySQL有哪些锁?

按锁粒度分类:

(1)⾏锁:锁某⾏数据,锁粒度最⼩,并发度⾼

(2)表锁:锁整张表,锁粒度最⼤,并发度低

(3)间隙锁:锁的是⼀个区间

还可以分为:

(1)共享锁:也就是读锁,⼀个事务给某⾏数据加了读锁,其他事务也可以读,但是不能写

(2)排它锁:也就是写锁,⼀个事务给某⾏数据加了写锁,其他事务不能读,也不能写

还可以分为:

(1)乐观锁:并不会真正的去锁某⾏记录,⽽是通过⼀个版本号来实现的

(2)悲观锁:上⾯所的⾏锁、表锁等都是悲观锁

在事务的隔离级别实现中,就需要利⽤锁来解决幻读

4.13、Mysql内链接、左连接、右连接区别

(1)内链接取两表的交集

(2)左连接取左表的全部+右表的交集部分

(3)右连接取右表的全部+左表的交集部分

4.14、where和having的区别?

where是约束声明,having是过滤声明,where早于having执行,并且where不可以使用聚合函数,having可以

4.15、如何设计数据库?

(1)抽取实体,比如学生信息、教师信息、课程信息等

(2)分析表中的字段属性

(3)分析表与表之间的关联

(4)遵循三大范式,设计主键,主键要小并且自增和不可修改

4.16、三大范式

(1)第一范式:每个列不可再分

(2)第二范式:在第一范式的基础上,非住建需要完全依赖主键,不可依赖主键的一部分

(3)第三范式:在第二范式的基础上,非住建只能依赖于主键不能依赖于其他非住建

4.17、char和varchar的区别

char是不可变的,最大长度为255,varchar是可变的字符串,最大长度为2^16

4.18、MySQL 删除自增 id,随后重启 MySQL 服务,再插入数据,自增 id 会从几开始?

(1)8之前,下次自增会取表中最大 id + 1。原理是最大id会记录在内存中,重启之后会重新读取表中最大的id

(2)8之后,仍从删除数据 id 后算起。原理是它将最大id记录在redolog里了

4.19、MySQL插入百万级的数据如何进行优化?

(1)可以减少些redolog日志和binlog日志的io次数

(2)保证数据按照索引进行有序插入

(3)可以分表后多线程进行插入

4.20、Select 语句完整的执行顺序 

(1)from 子句组装来自不同数据源的数据;

(2)where 子句基于指定的条件对记录行进行筛选;

(3)group by 子句将数据划分为多个分组;

(4)使用聚集函数进行计算;

(5)使用 having 子句筛选分组;

(6)计算所有的表达式;

(7)select 的字段;

(8)使用order by 对结果集进行排序。

4.21、索引的基本原理

索引⽤来快速地寻找那些具有特定值的记录。如果没有索引,⼀般来说执⾏查询时遍历整张表

索引的原理:

(1)就是把⽆序的数据变成有序的查询

(2)把创建了索引的列的内容进⾏排序

(3)对排序结果⽣成倒排表

(4)在倒排表内容上拼上数据地址链

(5)在查询的时候,先拿到倒排表内容,再取出数据地址链,从⽽拿到具体数据

4.22、索引覆盖是什么

索引覆盖就是⼀个SQL在执⾏时,可以利⽤索引来快速查找,并且此SQL所要查询的字段在当前索引对应的字段中都包含了,那么就表示此SQL⾛完索引后不⽤回表了,所需要的字段都在当前索引的叶⼦节点上存在,可以直接作为结果返回了

4.23、最左前缀原则是什么

当⼀个SQL想要利⽤索引是,就⼀定要提供该索引所对应的字段中最左边的字段,也就是排在最前⾯的字段,⽐如针对a,b,c三个字段建⽴了⼀个联合索引,那么在写⼀个sql时就⼀定要提供a字段的条件,这样才能⽤到联合索引,这是由于在建⽴a,b,c三个字段的联合索引时,底层的B+树是按照a,b,c三个字段从左往右去⽐较⼤⼩进⾏排序的,所以如果想要利⽤B+树进⾏快速查找也得符合这个规则

4.24、Mysql慢查询该如何优化?

(1)检查是否⾛了索引,如果没有则优化SQL利⽤索引

(2)检查所利⽤的索引,是否是最优索引

(3)检查所查字段是否都是必须的,是否查询了过多字段,查出了多余数据

(4)检查表中数据是否过多,是否应该进⾏分库分表了

(5)检查数据库实例所在机器的性能配置,是否太低,是否可以适当增加资源

4.25、binlog、redolog、undolog区别

Binlog(二进制日志)、redolog(重做日志)和undolog(回滚日志)是数据库管理系统中的三种不同类型的日志,它们各自有不同的作用和存储方式。

(1)Binlog(二进制日志)

  • 作用:记录数据库的所有更新操作,包括数据修改、DDL操作等。
  • 记录时机:在事务提交前或事务发生崩溃时生成。
  • 记录内容:以事件形式记录,包括SQL语句和数据变更等。
  • 应用场景:数据恢复、主从复制等。
  • 存储方式:通常以文件形式存储在磁盘上。

(2)RedoLog(重做日志)

  • 作用:确保事务的持久性,即在数据库发生故障时,通过重做RedoLog中的记录来恢复事务已提交的数据。
  • 记录时机:在事务执行过程中不断生成和写入。
  • 记录内容:记录数据页的修改情况,通常采用物理日志+逻辑日志的方式。
  • 存储方式:通常以文件形式存储在磁盘上,并可能在事务提交后刷新到磁盘。

(3)UndoLog(回滚日志)

  • 作用:保存事务发生之前的数据版本,用于事务回滚和并发控制。
  • 记录时机:在事务执行期间不断生成和写入。
  • 记录内容:记录事务对数据库进行的修改操作的撤销信息。
  • 存储方式:通常以数据页或回滚段的形式存储在数据库的数据文件中。

总结来说,Binlog用于记录数据库的操作事件,用于数据恢复和主从复制;RedoLog用于确保事务的持久性,通过重做日志中的记录来恢复数据;UndoLog用于事务回滚和并发控制,保存事务发生之前的数据版本。它们在数据库操作过程中触发的时机和记录的内容有所不同,且各自有不同的存储方式。

、常用的开发框架

5.1、什么是Spring?

Spring是一个轻量级的开发框架,通过IOC达到送耦合的目的,通过AOP使业务代码和系统服务进行内聚性开发,但是Spring需要通过配置文件配置各种组件,所以很繁琐,后面就出现了SpringBoot框架

5.2、IOC是什么?

ioc是控制反转,是一种思想,它是将方法的创建和调用从程序员手中交给ioc容器管,理降低对象之间的依赖关系。创建一个Bean的方式有:xml方式、@Bean注解、@Componte方式

5.3、AOP是什么?

AOP是面向切面编程,它是将非业务代码但又有很多业务代码需要调用的代码抽取出来,在不侵入原有代码的情况下对功能进行增强。

SpringAOP是基于动态代理实现的,动态代理是有两种,一种是jdk动态代理,一种是cglib动态代理;

jdk动态代理是原理是利用反射来实现的,需要调用反射包下的Proxy类的newProxyInstance方法来返回代理对象,这个方法中有三个参数,分别是用于加载代理类的类加载器,被代理类实现的接口的class数组和一个用于增强方法的InvocaHandler实现类。

cglib动态代理原理是利用asm开源包来实现的,是把被代理类的class文件加载进来,通过修改它的字节码生成子类来处理

jdk动态代理要求被代理类必须有实现的接口,生成的动态代理类会和代理类实现同样的接口,cglib则,生成的动态代理类会继承被代理类。Spring默认使用jdk动态代理,当被代理的类没有接口时就使用cglib动态代理

5.4、DI是什么?

对Bean实例化后,要对它的属性进行填充,我们就需要@Autowire直接的填充依赖注入的,它是优先按照类型进行填充(另一个是按照名称进行填充)

5.5、Spring中如何给对象属性赋值?

(1)通过构造函数

(2)通过set方法赋值

(3)通过p名称空间

5.6、Spring Bean的加载过程包括那些步骤?

Spring Bean 的加载过程主要包括解析和读取配置文件、实例化 Bean 对象、注入依赖关系、初始化 Bean 和使用、销毁等步骤。

5.7、如何定义一个全局异常处理器?

(1)我们要在类上加上@ContaollerAdvice注解,然后定义一些捕捉不同异常的方法,在这些方法上添加@ExceptionHandler(value = 异常类型.class)和@ResponseBody注解,方法参数是HttpServletRequest和异常类型,然后将异常消息进行处理。

(2)如果我们需要自定义异常的话,就写一个自定义异常类,该类需要继承一个异常接口,类属性包括final类型的连续id、错误码、错误信息,再根据需求写构造方法

5.8、如何使用aop自定义日志?

(1)创建一个切面类,把她添加到ioc容器中并添加@Aspect注解

(2)在切面类中写一个通知方法,在方法上添加通知注解并通过切入点表达式来表示要对哪些方法进行日志打印,然后方法参数为JoinPoint

(3)通过JoinPoint这个参数可以获取当前执行的方法名、方法参数等信息,这样就可以根据需求在方法进入或结束时打印日志

5.9、Spring IOC创建对象有哪些方式?

(1)通过无参构造

(2)带参构造

(3)共厂创建

5.10、Spring的配置方式?

(1)xml配置

(2)注解配置

(3)java配置

5.11、Spring Bean 的作用域有哪些?它们在 Bean 加载和使用过程中有何不同?

Spring Bean 的作用域包括 singleton、prototype、request、session 和 global session 等,它们在 Bean 加载和使用过程中的主要区别在于是否共享 Bean 实例、生命周期长度及作用域范围

5.12、Spring如何支持注解配置Bean,常用注解有哪些?

Spring 支持使用 @ComponentScan、@Configuration、@Bean、@Autowired 等注解进行 Bean 的自动扫描、注入和配置等操作。常用的注解还包括 @Service、@Controller、@Repository、@Qualifier 等。

5.13、什么是循环依赖,怎么去解决?

循环依赖是指一个或者多个Bean实例之间存在直接或者间接的依赖关系构成一个循环调用,通常表现为三种形态:互相依赖,A依赖B,B依赖A;间接依赖,两个或者两个以上的Bean存在间接依赖的关系,造成一个循环调用的局面;自我依赖,也就是自己依赖自己造成一种循环依赖。为了解决这个问题Spring就设计了三级缓存来解决部分循环依赖问题,所谓三级缓存就是用来存放不同类型的Bean,将Bean的实例化和Bean中属性的依赖注入这两个过程分离开来。

一级缓存:完全初始化好的Bean,可以直接被使用

二级缓存:原始Bean的对象,这个Bean里面的属性被实例化了,但是还未被属性赋值也就是说还未被依赖注入

三级缓存:存放的是Bean工厂的一个对象用来生成原始Bean对象并放入二级缓存中

d8a11258d71f479aae14a1470a39892b.png

当BeanA和BeanB之间存在一个循环依赖,首先我们需要初始化BeanA,先把BeanA实例化然后把BeanA包装成一个ObjectFactory对象保存到三级缓存里面,接下来BeanA开始对它大一个成员属性BeanB进行一个依赖注入,于是开始了初始化BeanB。首先需要创建BeanB的一个实例化,将BeanB的实例化对象加入到三级缓存里面,然后BeanB也进行了依赖注入,在三级缓存里面找到BeanA 的实例进行依赖注入,这样BeanB初始化成功了,就保存到一级缓存中,于是BeanA也可以成功拿到BeanB的一个实例,从而去完成正常的依赖注入,整个过程就是把Bean的实例化和Bean中属性的依赖注入这两个过程分离出来

5.14、Spring中的Bean创建的⽣命周期有哪些步骤  Bean的生命周期

(1)实例化

(2)填充属性,就是依赖注入

(3)初始化

(4)销毁

5.15、Spring事务原理

(1)spring事务有编程式和声明式,我们一般使用声明式,在某个方法上增加@Transactional注解,这个方法中的sql会统一成功或失败。

(2)原理是:当一个方法加上@Transactional注解,spring会基于这个类生成一个代理对象并将这个代理对象作为bean,当使用这个bean中的方法时,如果存在@Transactional注解,就会将事务自动提交设为false,然后执行方法,执行过程没有异常则提交,有异常则回滚

5.16、Spring事务的失效场景?

(1)事务方法所在的类没有加载到容器中

(2)事务方法不是public类型

(3)数据库不支持事务

(4)在同一类中,一个没有添加事务的方法调用另一个添加了事务的方法,事务不生效

(5)业务自己捕捉了异常,程序认为代码正常执行

5.17、Spring事务的隔离级别?

(1)它的隔离级别是和mysql数据库一样

(2)default:默认级别,使用数据库自定义的隔离级别

5.18、Spring事务的传播行为

(1)支持当前事务,如果不存在,则新启一个事务

(2)支持当前事务,如果不存在,则抛出异常

(3)支持当前事务,如果不存在,则以非事务方式执行

(4)不支持当前事务,创建一个新事物

(5)不支持当前事务,如果已存在事务就抛异常

(6)不支持当前事务,始终以非事务方式执行

5.19、Spring IOC的初始化过程

8256f4e40598461592a2eae4386c1d6d.png

5.20、Spring用了那些设计?

(1)BeanFactory用了工厂模式

(2)AOP用了动态代理模式

(3)RestTemplate用来模板方法模式

(4)SpringMVC中handlerAdaper用来适配器模式

(5)Spring里的监听器用了观察者模式

5.21、SpringMVC工作原理

SpringMVC工作过程围绕着前端控制器DispatchServerlet,几个重要组件有HandleMapping(处理器映射器)、HandleAdapter(处理器适配器)、ViewReslover(试图解析器)

工作流程:

(1)DispatchServerlet接收用户请求将请求发送给HandleMapping

(2)HandleMapping根据请求url找到具体的handle和拦截器,返回给DispatchServerlet

(3)DispatchServerlet调用HandleAdapter,HandleAdapter执行具体的controller,并将controller返回的ModelAndView返回给DispatchServler

(4)DispatchServerlet将ModelAndView传给ViewReslover,ViewReslover解析后返回具体view

(5)DispatchServerlet根据view进行视图渲染,返回给用户

5.22、Springmvc中拦截器和过滤器有什么区别 ?

(1)拦截器是Springmvc的组件,而过滤器是Servlet组件

(2)拦截器不依赖于容器,过滤器依赖容器

(3)拦截器只能对控制器请求起作用,而过滤器可以对所有请求起作用。

(4)拦截器可以获取ioc容器中的各个Bean对象,而过滤器不太方便

5.23、Mapper的实现原理 ?

(1)将Mapper接口生成动态代理对象

(2)找到Mapper的xml文件中和接口名对应的namespace ,并将namespace中的SQL语句进行缓存

(3)当使用Mapper代理对象调用方法时, 寻找相应的namespace中id和方法名相同的SQL进行执行

(4)将获取的SQL结果集进行封装,并返回

5.24、Spring中的常用注解有哪些 ???

(1)@Bean

(2)@Component

(3)@Service

(4)@Repository

(5)@Configuration

(6)@AutoWired按照类型自动装配

(7)@Qualifier指定Bean的标识

(8)@Transactional

(9)@Value

(10)@ComponentScan

(11)@ImportResource

(12)@Primary 按照类型自动注入时优先选择注入

5.25、Springmvc中的常用注解有哪些 ?

(1)@Controller

(2)@RestController

(3)@RequestBody

(4)@ResponseBody

(5)@RequestParam

(6)@ControllerAdvice

(7)@PathVarible

5.26、springboot自动配置原理

在spring—boot—autoconfigura包下存放了spring内置的自动配置类和spring.factories文件,这个文件中存放了这些配置类的全类名 ;

启动类@SpringbootApplication注解下,有三个关键注解

(1)@springbootConfiguration:表示启动类是一个自动配置类

(2)@CompontScan:扫描启动类所在包下及子包的组件到容器中

(3)@EnableConfigutarion,下面有个子注解@Import会导入上面所说的自动配置类,这些配置类会根据元注解的装配条件生效,生效的类就会被实例化,加载到ioc容器中;这些自动配置类还会通过xxxProperties文件里配置来进行属性设值

5.27、springboot常用注解

(1)@RestController:修饰类,该控制器会返回Json数据

(2)@RequestMapping("/path") :修饰类,该控制器的请求路径

(3)@Autowired:  修饰属性,按照类型进行依赖注入

(4)@PathVariable:  修饰参数,将路径值映射到参数上

(5)@ResponseBody:修饰方法,该方法会返回Json数据

(6)@RequestBody(需要使用Post提交方式):修饰参数,将Json数据封装到对应参数中

(7)@Controller@Service@Compont:  将类注册到ioc容器

(8)@Transaction:开启事务

5.28、spring的bean是线程安全的吗?

(1)spring的默认bean作用域是单例的,单例的bean不是线程安全的,但是开发中大部分的bean都是无状态的,不具备存储功能,比如controller、service、dao,他们不需要保证线程安全。

(2)如果要保证线程安全,可以将bean的作用域改为prototype,比如像Model View。

(3)另外还可以采用ThreadLocal来解决线程安全问题。ThreadLocal为每个线程保存一个副本变量,每个线程只操作自己的副本变量。

5.29、SpringBoot和SpringMVC的区别?

Springboot已经包含了SpringMVC , 并且将配置全部采用注解来代替。

5.30、springcloud主要解决什么问题?

解决服务之间的通信、容灾、负载平衡、冗余问题,能方便服务集中管理,常用组件有注册中心、配置中心、远程调用。服务熔断、网关

5.31、SpringBoot配置文件有哪些 怎么实现多环境配置 

Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件

5.32、SpringBoot和SpringCloud是什么关系

Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务,Spring Cloud是一个基于Spring Boot实现的开发工具;Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架; Spring Boot使用了默认大于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,Spring Cloud很大的一部分是基于Spring Boot来实现,必须基于Spring Boot开发。

5.33、SpringCloud都用过哪些组件 介绍一下作用

(1)Nacos--作为注册中心和配置中心,实现服务注册发现和服务健康监测及配置信息统一管理

(2)Gateway--作为网关,作为分布式系统统一的出入口,进行服务路由,统一鉴权等

(3)OpenFeign--作为远程调用的客户端,实现服务之间的远程调用

(4)Sentinel--实现系统的熔断限流

(5)Sleuth--实现服务的链路追踪

5.34、谈一谈Shiro  ?

Shiro是一个强大的Java安全框架,可以帮我们实现登录验证,权限管理等功能。

Shiro使用步骤 :

(1)配置Shiro,使其使用Redis作为缓存,并且设置一些放行策略等

(2)在我们自定义的login方法中,构建一个userpasswordToken , 执行subject.login,传入token。

(3)自定义Realm ,在认证方法中查询数据库该用户的信息是否匹配 。如果通过,将用户信息和权限等信息一起让Shiro保存起来。

(4)在鉴权方法中,从存储的信息中取出权限信息,封装起来交给Shiro

(5)每次执行验证,例如注解 ,都会调用鉴权方法一次。

5.35、为什么TCP不能两次握手?

三次握手

1374574b3abb4cb8bb5e1d733743e3fd.png

假设是两次握手,若客户端发起的连接请求阻塞在网络中,会造成该报文的重传,这时服务收到连接请求后会立刻进入连接状态,当双方传输完数据结束连接后,第一次阻塞的请求突然又到达了服务端,此时服务端又进入连接状态,而客户端不会响应服务端的连接确认报文

5.36、为什么要进入时间等待状态?

e91af1cb9c684c199bd84082b572c366.png

若客户端发送确认释放包后直接关闭,而服务端因为某种原因没有收到客户端的确认释放包,就会一直发送确认请求,而客户端永远不会再响应该请求

5.37、Spring Boot是如何启动Tomcat的

(1)⾸先,SpringBoot在启动时会先创建⼀个Spring容器

(2)在创建Spring容器过程中,会利⽤@ConditionalOnClass技术来判断当前classpath中是否存在Tomcat依赖,如果存在则会⽣成⼀个启动Tomcat的Bean

(3)Spring容器创建完之后,就会获取启动Tomcat的Bean,并创建Tomcat对象,并绑定端⼝等,然后启动Tomcat

5.38、socket通信流程

(1)服务端创建socket并调用bind()方法绑定ip和端口号

(2)服务端调用listen()方法建立监听,此时服务的scoket还没有打开

(3)客户端创建socket并调用connect()方法像服务端请求连接

(4)服务端socket监听到客户端请求后,被动打开,调用accept()方法接收客户端连接请求,当accept()方法接收到客户端connect()方法返回的响应成功的信息后,连接成功

(5)客户端向socket写入请求信息,服务端读取信息

(6)客户端调用close()结束链接,服务端监听到释放连接请求后,也结束链接

六、MyBatis框架

6.1、mybatis是什么,有什么作用?

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

6.2、Mybatis中ResultType和ResultMap的区别?

(1)Mybatis操作数据库时,ResultType指的是将结果映射成为什么类型 ,可以是Integer , String , 对象类型等

(2)如果是对象类型,需要对象属性和列别名相同。

(3)ResultMap指的是将SQL执行结果,根据指定的Map映射关系进行封装成实体

6.3、Mybatis中有哪些常用的动态标签 ?

(1)<where> 在<if>判断后的SQL语句前面添加WHERE关键字,并处理SQL语句开始位置的AND 或者OR的问题

(2)<if> 进行条件的判断

(3)<Set>主要用于修改操作时出现的逗号问题

(4)<foreache>  迭代操作

(5)<trim> 可以在SQL语句前后进行添加指定字符 或者去掉指定字符

(6)<choose><where><otherwise> 类似于java中的switch语句.在所有的条件中选择其一

6.4、Mybatis 中一级缓存与二级缓存

MyBatis的缓存分为一级缓存和 二级缓存

(1)一级缓存是SqlSession级别的缓存,默认开启。

(2)二级缓存是NameSpace级别(Mapper)的缓存,多个SqlSession可以共享,使用时需要进行配置开启。

(3)缓存的查找顺序:二级缓存 => 一级缓存 => 数据库

6.5、mapper的xml文件中的namespace有什么作用 ?

(1)实现SQL的隔离,也就是说一个mapper接口类对应一组SQL语句

(2)实现Mybatis的二级缓存

6.6、Mybatis中 #{} 和 ${} 的区别?

(1)${}的sql是静态sql,将参与的sql语句进拼接,然后执行

(2)#{}的sql是动态sql,会提前将sql预编译,#{}使用?占位符代替

七、Redis篇

7.1、redis为什么快?

(1)完全基于内存操作

(2)数据结构简单,对数据操作简单

(3)redis执行命令是单线程的,避免了上下文切换带来的性能问题,也不用考虑锁的问题

(4)采用了非阻塞的io多路复用机制,使用了单线程来处理并发的连接;内部采用的epoll+自己实现的事件分离器

(5)其实Redis不是完全多线程的,在核心的网络模型中是多线程的用来处理并发连接,但是数据的操作都是单线程。Redis坚持单线程是因为Redis是的性能瓶颈是网络延迟而不是CPU,多线程对数据读取不会带来性能提升。

7.2、redis持久化机制

(1)快照持久化RDB

redis的默认持久化机制,通过父进程fork一个子进程,子进程将redis的数据快照写入一个临时文件,等待持久化完毕后替换上一次的rdb文件。整个过程主进程不进行任何的io操作。持久化策略可以通过save配置单位时间内执行多少次操作触发持久化。所以RDB的优点是保证redis性能最大化,恢复速度数据较快,缺点是可能会丢失两次持久化之间的数据

(2)追加持久化AOF

以日志形式记录每一次的写入和删除操作,策略有每秒同步、每次操作同步、不同步,优点是数据完整性高,缺点是运行效率低,恢复时间长

7.3、Redis如何实现key的过期删除?

采用的定期过期+惰性过期

(1)定期删除 :Redis 每隔一段时间从设置过期时间的 key 集合中,随机抽取一些 key ,检查是否过期,如果已经过期做删除处理。

(2)惰性删除 :Redis 在 key 被访问的时候检查 key 是否过期,如果过期则删除。

7.4、Redis数据类型应用场景

(1)String Map<String,String>可以用来缓存json信息,Redis分布式锁的实现就利⽤了这种数据结构,还包括可以实现计数器、Session共享、分布式ID

(2)Hash Map<String,Map<String,Object>>与String一样可以保存json信息,更适合⽤来存储对象

(3)List Map<String,List<Object>>可以用来做消息队列,list的pop是原子性操作能一定程度保证线程安全,可以⽤来缓存类似微信公众号、微博等消息流数据

(4)Set Map<String,Set<Object,Object>>可以做去重,比如一个用户只能参加一次活动 ,可以实现类似我和某⼈共同关注的⼈、朋友圈点赞等功能

(5)SortSet Map<String,TreeSet<String>>有序的。可以实现排行榜

7.5、Redis缓存穿透如何解决?

缓存穿透是指频繁请求客户端和缓存中都不存在的数据,缓存永远不生效,请求都到达了数据库。

解决方案:

(1)在接口上做基础校验,比如id<=0就拦截

(2)缓存空对象:找不到的数据也缓存起来,并设置过期时间,可能会造成短期不一致

(3)布隆过滤器:在客户端和缓存之间添加一个过滤器,拦截掉一定不存在的数据请求

7.6、Redis如何解决缓存击穿?

缓存击穿是值一个key非常热点,key在某一瞬间失效,导致大量请求到达数据库

解决方案:

(1)设置热点数据永不过期

(2)给缓存重建的业务加上互斥锁,缺点是性能低

7.7、Redis如何解决缓存雪崩?

缓存雪崩是值某一时间Key同时失效或redis宕机,导致大量请求到达数据库

解决方案:

(1)搭建集群保证高可用

(2)进行数据预热,给不同的key设置随机的过期时间

(3)给缓存业务添加限流降级,通过加锁或队列控制操作redis的线程数量

(4)给业务添加多级缓存

7.8、Redis分布式锁的实现原理

原理是使用setnx+setex命令来实现,但是会有一系列问题:

(1)任务时常超过缓存时间,锁自动释放。可以使用Redision看门狗解决

(2)加锁和释放锁的不是同一线程。可以在Value中存入uuid,删除时进行验证。但是要注意验证锁和删除锁也不是一个原子性操作,可以用lua脚本使之成为原子性操作

(3)不可重入。可以使用Redision解决(实现机制类似AQS,计数)

(4)redis集群下主节点宕机导致锁丢失。使用红锁解决

7.9、Redis集群主从同步原理

(1)主从同步第一次是全量同步:slave第一次请求master节点会根据replid判断是否是第一次同步,是的话master会生成RDB发送给slave。

(2)后续为增量同步:在发送RDB期间,会产生一个缓存区间记录发送RDB期间产生的新的命令,slave节点在加载完后,会持续读取缓存区间中的数据

7.10、Redis缓存一致性解决方案

(1)Redis缓存一致性解决方案主要思考的是删除缓存和更新数据库的先后顺序

(2)先删除缓存后更新数据库存在的问题是可能会数据不一致,一般使用延时双删来解决,即先删除缓存,再更新数据库,休眠X秒后再次淘汰缓存。第二次删除可能导致吞吐率降低,可以考虑进行异步删除。

(3)先更新数据库后删除缓存存在的问题是会可能会更新失败,可以采用延时删除。但由于读比写快,发生这一情况概率较小。

(4)但是无论哪种策略,都可能存在删除失败的问题,解决方案是用中间件canal订阅binlog日志提取需要删除的key,然后另写一段非业务代码去获取key并尝试删除,若删除失败就把删除失败的key发送到消息队列,然后进行删除重试。

7.11、Redis内存淘汰策略

当内存不足时按设定好的策略进行淘汰,策略有:

(1)淘汰最久没使用的

(2)淘汰一段时间内最少使用的

(3)淘汰快要过期的

7.12、Redis的使用场景

(1)token的存储:结合Shiro ,或者Cloud中结合Oauth2.0授权中心

(2)请求数据的存储:非分页数据都可以存

(3)分布式锁:setNx , redission , Redlock

(4)手机验证码:浏览器请求服务器,服务器生成一个验证码 ,以手机号为key存入redis设置有效时间,并将消息丢入Mq,返回结果。Mq消费方收到消息,调用cms服务商的接口,发短信。用户输入验证码后,将验证码和redis中的进行对比

(5)布隆过滤器

八、组件集合篇

8.1、Nginx是干什么的 ,有什么作用 ?

 (1)静态HTTP服务器

        可以将静态文件,html、图片等部署在Nginx中,通过http协议展现给客户端

(2)反向代理服务器

        浏览器请求Nginx服务器 ,Nginx服务器请求应用服务器 ,最终将结果返回给浏览器。反向服务器指的就是浏览器不需要知道自己访问的是哪个服务器,由Nginx来决定

(3)负载均衡

        1、当应用服务器部署了多份时, 可以通过Nginx做一个负载均衡。

        2、负载均衡算法有:轮询(默认)、带权轮询、ip_hash(按ip哈希结果分配,能解决session共享问题)、url_hash(按访问的URL的哈希结果分配)、fair(根据服务端响应时间分配,响应时间短优先)

(4)虚拟主机

        1、有的网站访问量大,需要负载均衡。然而并不是所有网站都如此出色,有的网站,由于访问量太小,需要节省成本,将多个网站部署在同一台服务器上。

        2、例如将AAA International Relationshttp://www.bbb.com两个网站部署在同一台服务器上,两个域名解析到同一个IP地址,但是用户通过两个域名却可以打开两个完全不同的网站,互相不影响,就像访问两个服务器一样,所以叫两个虚拟主机。

(5)FastCGI

8.2、Nginx反向代理怎么用 ? 什么时候用 ?

(1)配置Nginx配置文件 。

(2)不暴露服务器的真实地址,多个web或gateway负载均衡时

8.3、Nginx实现动静分离 ?

(1)什么是动态请求?

比如说我们需要去查询数据库,从数据库中返回需要的信息,那么这种请求我们就可以使用 nginx 直接给转发到 tomcat 去处理,这种请求也叫动态请求

(2)什么是静态请求?

比如说我们现在需要去请求得到一个图片的地址,或者得到一个 html 文件,这种请求就叫静态请求,这时候就可以使用 nginx ,把请求去指向一个静态资源服务器。

配置Nginx的Server即可

8.1、RabbitMq的使用场景和作用

使用场景:

(1)手机验证码

(2)微信小程序推送

(3)商品秒杀

(4)记录日志

作用:

削峰、限流、异步、解耦

8.2、Mq有哪几种模式?

(1)hello模型   1v1

(2)word模型   1vn负载均衡

(3)直连模型    1vn通过交换机和路由key全匹配模式

(4)广播模型      1vn广播

(5)主题模型     1vn通过交换机和路由key站位符 *#通配

8.3、怎么保证Mq的消息不丢失,如何监视状态,如何确保安全 ?

(1)RabbitMq提供了一个消息监视功能,可以知道消息到达了哪个环节。我们可以根据这个定位

(2)消息发送到交换机里,可以监视,设置回调方法

(3)消息抵达队列,可以监视,也可以设置回调方法

(4)可以单独建一张表,发送消息时插入一条消息数据,然后抵达交换机时,抵达队列,被消费时分别去修改该消息状态。这样就可以追踪消息的情况了

8.4、Mq消息签收机制

签收机制分为两种:

(1)自动签收

(2)手动签收

手动签收又分为三种:

不管,当我们不管这个消息的时候,自动回到签收队列中,如果重启,消费者会重复消费

签收,当消息成功被消费以后,就被签收了,消息就从队列中删除

拒绝,当消息被拒签后,消息回到队列中,相当于一个新的消息,消息会被消费者重复消费,形成死循环,知道签收为止(一般在重要的业务下结合自定义重试机制使用,将重试次数记录到数据库或者Redis中,最后签收或者人工进行处理)

8.5、死信队列是什么?延时队列是什么?

(1)死信队列也是⼀个消息队列,它是⽤来存放那些没有成功消费的消息的,通常可以⽤来作为消息重试

(2)延时队列就是⽤来存放需要在指定时间被处理的元素的队列,通常可以⽤来处理⼀些具有过期性操作的业务,⽐如⼗分钟内未⽀付则取消订单

8.6、RabbitMQ如何保证消费顺序?

(1)RabbitMQ消费顺序乱了是因为消费者集群拿到消息后对消息处理速度不同导致的,比如可能将增删改变成了增改删。

(2)解决方法:为RabbitMQ创建多个Queue,每个消费者只监听其中一个Queue,同一类型的消息都放在一个queue中,同一个 queue 的消息是一定会保证有序的。

8.1、设计模式六大原则

(1)单一职责原则:一个类或者一个方法只负责一项职责,尽量做到类只有一个行为引起变化;
(2)里氏替换原则:子类可以扩展父类的功能,但不能改变原有父类的功能
(3)依赖倒置原则:高层模块不应该依赖底层模块,两者都应该依赖接口或抽象类
(4)接口隔离原则:建立单一接口,尽量细化接口
(5)迪米特原则:只关心其它对象能提供哪些方法,不关心过多内部细节
(6)开闭原则:对于拓展是开放,对于修改是封闭的

8.2、设计模式分类

(1)创建型模式:主要是描述对象的创建,代表有单例、原型模式、工厂方法、抽象工厂、建造者模式

(2)结构型模式:主要描述如何将类或对象按某种布局构成更大的结构,代表有代理、适配器、装饰

(3)行为型模式:描述类或对象之间如何相互协作共同完成单个对象无法完成的任务,代表有模板方法模式、策略模式、观察者模式、备忘录模式

8.1、hash表冲突的解决方法

(1)开放地址法:有线性探测法和平方探测法,当发生冲突时,继续往后找
(2)再哈希法:构造多个哈希函数,发生冲突后使用下一个函数
(3)链地址法:将hash值相同的记录用链表链接起来
(4)建立公共溢出区:将哈希表分为基础表和益处表两部分,发生冲突的填入益处表

8.1、nacos的功能

一、客户端

(1)注册自身服务

(2)获取服务列表

(3)获取配置信息

(4)维持心跳信息

二、服务器

(1)管理注册服务

(2)向客户端提供服务列表

(3)保存并提供客户端配置信息

(4)服务治理功能

三、服务注册原理

服务注册的策略的是每5秒向nacos server发送一次心跳,心跳带上了服务名,服务ip,服务端口等信息。同时 nacos server也会向client 主动发起健康检查,支持tcp/http检查。如果15秒内无心跳且健康检查失败则认为实例不健康,如果30秒内健康检查失败则剔除实例。

8.1、Linux常用命令

(1)ifconfig:查看网络接口详情

(2)ping:查看与某主机是否能联通

(3)ps -ef|grep 进程名称:查看进程号

(4)lost -i 端口 :查看端口占用情况

(5)top:查看系统负载情况,包括系统时间、系统所有进程状态、cpu情况

(6)free:查看内存占用情况

(7)kill:正常杀死进程,发出的信号可能会被阻塞

(8)kill -9:强制杀死进程,发送的是exit命令,不会被阻塞

8.1、ribbon是什么?

是一个基于HTTP和TCP的客户端负载均衡工具,主要功能是提供客户端负载均衡算法和服务调用。Ribbon 作为服务消费者的负载均衡器,有两种使用方式,一种是和RestTemplate服务远程调用相结合,另一种是和OpenFeign相结合。OpenFeign 已经默认集成了Ribbon

8.2、ribbon的负载均衡

(1)拦截到这个亲求

(2)截取到url地址

(3)结合Eureka做服务发现,拿到list集合

(4)结合负载均衡算法拿到一个符合的实例ip和端口

(5)重构url地址

(6)发送一个请求

8.1、Zookeeper的理解 ?

Zookeeper是一个开源的分布式协调服务,作用有:

(1)集群管理,容错,负载均衡

(2)配置文件的集中管理

(3)RPC的注册中心,搭配Dubbo使用

分布式锁

(1)简单来说,Zookeeper就是文件系统 + 通知监听机制 ,底层数据结构是树结构,树存储的节点叫Znode .

(2)Znode分为四种类型 :持久化节点,持久化顺序编号目录节点,临时节点,临时顺序编号目录节点

(3)Zookeeper搭建集群,要求节点数为奇数,也就是说服务器需要为奇数台,一台服务器一个节点,最少需要三台,因为Zookeeper有三个角色。

(4)Zookeeper分为Leader , Follower , Observer 3个角色 , Leader可以提供读写服务 ,Follower和Observer提供读取服务,Observer不参与投票选举Leader 。

为什么Zookeeper可以⽤来作为注册中⼼

可以利⽤Zookeeper的临时节点和watch机制来实现注册中⼼的⾃动注册和发现,另外Zookeeper中的数据都是存在内存中的,并且Zookeeper底层采⽤了nio,多线程模型,所以Zookeeper的性能也是⽐较⾼的,所以可以⽤来作为注册中⼼,但是如果考虑到注册中⼼应该是注册可⽤性的话,那么Zookeeper则不太合适,因为Zookeeper是CP的,它注重的是⼀致性,所以集群数据不⼀致时,集群将不可⽤,所以⽤Redis、Eureka、Nacos来作为注册中⼼将更合适。

8.2、Dubbo的理解 , 配置 ,熔断 ?

(1)Dubbo是阿里巴巴的远程调用框架,其中原理是首先将服务提供者注册到注册中心,消费方连接注册中心,获取到服务提供者的IP端口等信息,然后发起单一的长连接,实现远程调用。

(2)熔断是为了防止服务雪崩,一般使用hystrix ,加注解写一个处理调用失败的方法进行兜底即可,hytrix发生在消费端。

Dubbo⽀持哪些负载均衡策略

(1)随机:从多个服务提供者随机选择⼀个来处理本次请求,调⽤量越⼤则分布越均匀,并⽀持按权重设置随机概率

(2)轮询:依次选择服务提供者来处理请求, 并⽀持按权重进⾏轮询,底层采⽤的是平滑加权轮询算法

(3)最⼩活跃调⽤数:统计服务提供者当前正在处理的请求,下次请求过来则交给活跃数最⼩的服务器来处理

(4)⼀致性哈希:相同参数的请求总是发到同⼀个服务提供者

8.3、ES在项目中是怎么用的?

底层实现是红黑二叉树

(1)商品的存放(方便用户检索)

(2)日志统计(整个应用的日志非常多,都要收集起来,方便后期做分析)

(3)数据分析(大数据分析),主要是爬虫得到的数据存储在其中,并将数据进行分析,价格对比,竞品分析等。

(4)个人备注: MongoDB和ES ,MongoDB更注重数据的管理,CRUD 。而ES着重偏向于数据的搜索。

8.4、SpringCloud注册中心的作用 ?

Eureka可以作为Cloud的注册中心,保存了服务列表。concurrentHashMap<String,Map<String,List>>结构,

内层的key表示服务名称,而对应的value  List表示多个服务的集群。

Eureka可以完成的功能有:

(1)服务注册

当项目启动时(eureka的客户端),就会向eureka-server发送自己的元数据(运行的ip,端口port,健康的状态监控等,因为使用的是http/ResuFul请求风格),eureka-server会在自己内部保留这些元数据。(restful风格,以http动词的请求方式,完成对url资源的操作)

(2)服务续约 (更新时间戳)

项目启动成功了,除了向eureka-server注册自己成功,还会定时的向eureka-server汇报自己,表示自己还活着

(3)服务剔除 (定期将停止的服务删除)

当项目关闭时,会给eureka-server报告,说明自己要下机了

(4)服务下线  (将停止的服务从列表中移除)

当项目超过了指定时间没有向eureka-server汇报自己,那么eureka-server就会认为此节点死掉了,会把它剔除掉,也不会放流量和请求到此节点了

(5)服务发现 (通过服务的名称,找到服务的具体地址IP+端口)

在服务A里面调用B,通过服名称找到具体的IP和端口号

Eureka对外提供restful风格服务,只要利用这些接口就能完成服务的注册和发现。所以说,任何语言都可以完成服务的注册和发现,能发请求就行。

8.5、Eureka工作流程:

(1)Eureka Server 启动成功,等待服务端注册。在启动过程中如果配置了集群,集群之间定时通过 Replicate 同步注册表,每个 Eureka Server 都存在独立完整的服务注册表信息

(2)Eureka Client 启动时根据配置的 Eureka Server 地址去注册中心注册服务

(3)Eureka Client 会每 30s 向 Eureka Server 发送一次心跳请求,证明客户端服务正常

(4)当 Eureka Server 90s 内没有收到 Eureka Client 的心跳,注册中心则认为该节点失效,会注销该实例

(5)单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端

(6)当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式

(7)Eureka Client 定时全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地

(8)服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存

(9)Eureka Client 获取到目标服务器信息,发起服务调用

(10)Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除

8.6、Eureka和Zookeeper的区别 ?

什么是CAP原则 ?为什么Zookeeper不适合做注册中心 ?

CAP原则指的是分布式系统中的一致性,可用性,分区容错性。这三个要素最多只能同时实现两点,不可能三者兼得。

(1)C : 数据一致性,Zookeeper构成最小的注册中心需要三个角色,1个leader,1个follower ,1个observer ,集群只需要添加follower即可。Zookeeper注重数据的一致性,如果leader挂了,需要一定的时间才能选举出新的leader

eureka不是很注重数据的一致性,只要还有一个节点活着就可以对外提供服务,所以注重的是可用性。

(2)A : 服务的可用性

在Zookeeper中,若是Leader挂了,则整个集群都不会对外服务了,直到选举出新的leader(120S左右),才能继续对外提供服务

eureka注重服务的可用性,只要eureka集群还有一台存活,就可以对外提供服务。

(3)P : 分区容错性,在集群中的机器,因为网络原因,机房原因,可能导致数据不会及时同步,它是分布式必须要实现的特性

8.7、hystrix的作用 ,状态有哪些 ?

(1)hystrix是熔断器,作用是防止服务的雪崩。

(2)状态有闭合,半开,全开状态

8.8、SpringCloud的组件有哪些 ?

(1)Eureka:Eureka是微服务架构中的服务注册与发现组件。它允许服务在启动时注册到Eureka服务器,并且其他服务可以查询该服务器以获取可用服务的信息。这有助于实现动态服务的发现和负载均衡

(2)gateway:它用于管理微服务架构中的路由、过滤器等。网关可以处理诸如路由、负载均衡、安全性等方面的任务,同时还支持动态路由配置 

(3)openFeign:通过使用注解,开发者可以轻松定义和维护与其他服务的通信。它集成了Ribbon和Hystrix,从而实现了负载均衡和容错机制

(4)config:为分布式系统中的服务提供了集中化的外部配置支持,它允许将配置信息存储在中心服务器中,而各个微服务则可以从配置中心动态地获取配置信息。这样可以实现配置的集中管理和动态更新,而无需重新部署微服务。

(5)Dubbo:RPC调⽤

(6)Nacos:注册中⼼、配置中⼼

(7)Hystrix:服务熔断

8.9、微服务的理解 ,和分布式的区别 ?优缺点?

(1)传统分布式项目主要是对项目进行模块进行水平拆分部署 ,例如根据Service , Dao , Controller进行拆分,Controller的web部分和Service , Dao分开部署 ,Controller通过远程调用Service 。

(2)微服务也是对项目的拆分,不过是垂直拆分,也就是根据功能模块进行拆分 。Controller , Service , Dao都打包在一起进行部署。

(3)分布式的项目拆分较为简单,复杂度不高,运维成本低。

(4)微服务项目拆分太小,职责单一,可以自治,可灵活组合部署,运维成本高,因为模块太多。

8.10、网关里面写什么 ?

限流策略 , 简单的token校验(是否携带,redis中是否存在) ,手动路由(请求授权服务器),配置跨域

8.11、如何使用Eureka的 ?

(1)springCloud的注册中心

(2)config配置文件管理中心结合eureka,可以让服务模块根据服务名称找到config配置中心进行配置文件拉取

(3)openFeign结合Eureka实现根据服务名称就可以实现远程调用

8.12、负载均衡算法有哪些

(1)轮询法

(2)随机法

(3)源地址哈希法

(4)加权轮询法

(5)加权随机法

(6)最⼩连接数法

8.13、分布式架构下,Session 共享有什么⽅案

(1)采⽤⽆状态服务,抛弃session

(2)存⼊cookie

(3)服务器之间进⾏ Session 同步,这样可以保证每个服务器上都有全部的 Session 信息,不过当服务器数量⽐较多的时候,同步是会有延迟甚⾄同步失败

(4)IP 绑定策略,使⽤ Nginx (或其他复杂均衡软硬件)中的 IP 绑定策略,同⼀个 IP 只能在指定的同⼀个机器访问,但是这样做失去了负载均衡的意义,当挂掉⼀台服务器的时候,会影响⼀批⽤户的使⽤,⻛险很⼤;

(5)使⽤ Redis 存储,把 Session 放到 Redis 中存储,虽然架构上变得复杂,并且需要多访问⼀次 Redis ,但是这种⽅案带来的好处也是很⼤的:

                1、实现了 Session 共享;

                2、可以⽔平扩展(增加 Redis 服务器);

                3、服务器重启 Session 不丢失(不过也要注意 Session 在 Redis 中的刷新/失效机制);

                4、不仅可以跨服务器 Session 共享,甚⾄可以跨平台(例如⽹⻚端和 APP 端)

8.14、分布式系统中常⽤的缓存⽅案有哪些?

(1)客户端缓存:⻚⾯和浏览器缓存,APP缓存,H5缓存,localStorage 和 sessionStorage CDN缓存:内容存储:数据的缓存,内容分发:负载均衡

(2)nginx缓存:静态资源

(3)服务端缓存:本地缓存,外部缓存

(4)数据库缓存:持久层缓存(mybatis,hibernate多级缓存),mysql查询缓存 操作系统缓存:PageCache、BufferCache

8.15、什么是服务熔断?什么是服务降级?区别是什么?

(1)服务熔断是指,当服务A调⽤的某个服务B不可⽤时,上游服务A为了保证⾃⼰不受影响,从⽽不再调⽤服务B,直接返回⼀个结果,减轻服务A和服务B的压⼒,直到服务B恢复。

(2)服务降级是指,当发现系统压⼒过载时,可以通过关闭某个服务,或限流某个服务来减轻系统压⼒,这就是服务降级。

相同点:

(1)都是为了防⽌系统崩溃

(2)都让⽤户体验到某些功能暂时不可⽤

不同点:

(1)熔断是下游服务故障触发的,降级是为了降低系统负载

8.16、怎么拆分微服务?

(1)拆分微服务的时候,为了尽量保证微服务的稳定,会有⼀些基本的准则:

(2)微服务之间尽量不要有业务交叉。

(3)微服务之前只能通过接⼝进⾏服务调⽤,⽽不能绕过接⼝直接访问对⽅的数据。

(4)⾼内聚,低耦合。

8.17、如何用springSecurity做的认证授权?

(1)在数据库中有五张表,分别是菜单表,角色表,用户表,他们是多对多的关系,所以还有角色菜单表,角色用户表

(2)登录后进入认证过滤器,获取用户名和密码,根据用户名查询用户具有的权限并把用户名和对应权限信息放到redis,JWT生成token后放入cookie,每次调用接口时携带

(3)然后执行授权过滤器,从header中获取token解析出用户名,根据用户名从redis中获取权限列表,然后springSecurity就能够判断当前请求是否有权限访问

8.18、秒杀流程

用户点击下单按钮时,需要进行三次判断:

(1)先判断请求路径是否合法,因为做了动态URL。

(2)在判断用户是否已经下过单,就是看redis缓存中有没有用户下单的信息。

(3)最后判断库存,这里进行了redis库存预减,由于判断库存和预减库存不是一个原子性操作,所以可以用lua脚本来执行这一段代码,使其成为一个原子性。

然后从这里开始使用分布式锁,锁id为用户id+商品id,防止一个用户发送多次请求让redis多次预减。

Redis扣减成功后,进行异步下单,直接将正在处理返回给前端,将用户id和商品Id发送RabbitMQ中,负责下单的业务会从消息队列中拿出消息,去执行以下操作:

(1)减库存,减库存时用where 库存>0防止超卖

(2)订单表中生成记录,订单表中的用户id和商品id添加了联合唯一索引防止超卖,减库存和增加订单放在一个事务内保证一致性

(3)将用户id和订单id缓存到redis中用来最初对用户重复下单的判断

(4)释放分布式锁,根据value去判断锁是不是当前线程的,判断和删除锁不是原子性操作,所以封装到了lua脚本中

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值