Java八股文

Java基础

1.面向对象的三大特性?分别解释下?

封装,把客观的事物封装为抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的类或对象隐藏信息。简单的来说,一个类就是一个封装了数据和操作这些代码的实体。我们可以人为的设定某些代码私有,不能被外界访问。好处:对内部数据的不同级别包含,防止程序中无关的部分被意外被改变

继承,继承指它可以使用现有类的所有功能,并在无须编写原来的类的情况下对这些功能进行拓展,通过继承创建的新类(子类),被继承的类(父类)

多态:指一个类实例的相同方法在不同情形下有不同的表现形式
使得有不同内部结构的对象可以共享相同的外部接口「相同的消息可能会发送给多个不同类别的对象,而系统依赖对象所属类别,触发对应类别的方法,产生不同的行为」

2.JDK、JRE、JVM 三者之间的关系?

JDK、JRE、JVM 之间存在这样的包含关系:JDK包含 JRE,JRE又包含 JVM。
换句话说,只要安装了 JDK,JRE 和 JVM 则自动就安装了。① JDK:JDK(Java Development Kit) 是 Java 语言的软件开发工具包(SDK)。它是每一个 Java 软件开发人员必须安装的。JDK 安装之后,它会自带一个 JRE,因为软件开发人员编写完代码之后总是要运行的。注意:如果只是在这台机器上运行 Java 程序,则不需要安装 JDK,只需要安装 JRE 即可(JRE 是有独立安装包的,可以从 Oracle 官网上找一下)。② JRE:JRE(Java Runtime Environment,Java 运行环境),运行 JAVA程序所必须的环境的集合,包含 JVM 标准实现及 Java 核心类库。③ JVM:JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,JVM 是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM 是实现 Java 语言跨平台的法宝。

3.重载和重写的区别?

​重载
重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数。
①必须具有不同的参数列表。
②可以有不同的访问修饰符。
③可以抛出不同的异常。
重载是同一个类中方法之间的关系,是水平关系

重写
覆盖(也叫重写)是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,只是函数的实现体不一样。
①参数列表必须完全与被重写的方法相同,否则不能称其为重写而是重载。
②返回的类型必须一直与被重写的方法的返回类型相同,否则不能称其为重写而是重载。
③访问修饰符的限制一定要大于被重写方法的访问修饰符。
④重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。
重写是子类和父类之间的关系,是垂直关系

4.Java 中是否可以重写一个 private 或者 static 方法?

方法的重写规则:

  1. 参数列表必须完全与被重写方法的相同;
  2. 返回类型必须完全与被重写方法的返回类型相同;
  3. 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
  4. 父类的成员方法只能被它的子类重写。
  5. 声明为final的方法不能被重写。
  6. 声明为static的方法不能被重写,但是能够被再次声明。
  7. 子类和父类在同一个包中,那么子类可以重写父类所有除了声明为private和final的方法。
  8. 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
  9. 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛 出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  10. 构造方法不能被重写。
  11. 如果不能继承一个方法,则不能重写这个方法。
5. 构造方法有哪些特性?

java类中的一个用来初始化对象的方法,用new构造方法,创建一个新的对象,并可以给对象中的实例进行赋值。1.当没有指定构造方法时,系统会自动添加无参的构造方法。2.构造方法可以重载:方法名相同,但参数不同的多个方法,调用时会自动根据不同的参数选择相应的方法。3.构造方法是不被继承的4.当我们手动的指定了构造方法时,无论是有参的还是无参的,系统都将不会再添加无参的构造方法。

6.在 Java 中定义一个不做事且没有参数的构造方法有什么作用?

Java 程序在执行子类的构造方法之前,如果没有用 super(参数) 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super(参数) 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。

7.Java 中创建对象的几种方式?

new关键字
反射(2种)
克隆Clone()
反序列化

Integer 和 int 的区别?

1.int是java 八种数据类型之一,Integer 为int 的封装类;
2.int的默认值为0,而Integer的默认值为null,即Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况

装箱和拆箱的区别

装箱是将值类型转换为引用类型 ;装箱:用于在垃圾回收堆中储存值类型。装箱是值类型到Object类型或到此类型所实现的任何接口类型的隐式转换。
拆箱是将引用类型转换为值类型。拆箱:从object类型到值类型或从接口类型到实现该接口的值类型的显示转换。为何需要装箱?(为何要将值类型转为引用类型?)
一种最普通的场景是,调用一个含类型为Object的参数的方法,该Object可支持任意为型,以便通用。当你需要将一个值类型(如Int32)传入时,需要装箱。
另一种用法是,一个非泛型的容器,同样是为了保证通用,而将元素类型定义为Object。于是,要将值类型数据加入容器时,需要装箱。
装箱/拆箱的内部操作。
装箱:对值类型在堆中分配一个对象实例,并将该值复制到新的对象中。按三步进行。
第一步:新分配托管堆内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)。
第二步:将值类型的实例字段拷贝到新分配的内存中。
第三步:返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用了。
有人这样理解:如果将Int32装箱,返回的地址,指向的就是一个Int32。我认为也不是不能这样理解,但这确实又有问题,一来它不全面,二来指向Int32并没说出它的实质(在托管堆中)。
拆箱:
检查对象实例,确保它是给定值类型的一个装箱值。将该值从实例复制到值类型变量中。
有 书上讲,拆箱只是获取引用对象中指向值类型部分的指针,而内容拷贝则是赋值语句之触发。我觉得这并不要紧。最关键的是检查对象实例的本质,拆箱和装箱的类 型必需匹配,这一点上,在IL层上,看不出原理何在,我的猜测,或许是调用了类似GetType之类的方法来取出类型进行匹配(因为需要严格匹配)。
装箱/拆箱对执行效率的影响
显然,从原理上可以看出,装箱时,生成的是全新的引用对象,这会有时间损耗,也就是造成效率降低。
那该如何做呢?
首先,应该尽量避免装箱。
比如上例2的两种情况,都可以避免,在第一种情况下,可以通过重载函数来避免。第二种情况,则可以通过泛型来避免。
当然,凡事并不能绝对,假设你想改造的代码为第三方程序集,你无法更改,那你只能是装箱了。
对于装箱/拆箱代码的优化,由于C#中对装箱和拆箱都是隐式的,所以,根本的方法是对代码进行分析,而分析最直接的方式是了解原理结何查看反编译的IL代码。比如:在循环体中可能存在多余的装箱,你可以简单采用提前装箱方式进行优化。

switch 语句能否作用在 byte 上,能否作用在 long 上,能否作用在 String 上?

在switch(expr1)中,expr1只能是一个整数表达式或者枚举常量(更大字体),整数表达式可以是int基本类型或Integer包装类型,由于,byte,short,char都可以隐含转换为int,所以,这些类型以及这些类型的包装类型也是可以的。显然,long和String类型都不符合switch的语法规定,并且不能被隐式转换成int类型,所以,它们不能作用于swtich语句中。

final、finally、finalize 的区别

final:Java中的关键字,修饰符。
A). 如果一个类被声明为final,就意味着它不能再派生出新的子类,不能作为父类被继承。因此,一个类不能同时被声明为abstract抽象类的和final的类。
B). 如果将变量或方法声明为final,可以保证它们在使用中不被改变。
1)被声明为final的变量必须在声明时给定初始值,而在以后的引用中只能读取,不可修改。
2)被声明final的方法只能使用,不能重载。
finally:Java的一种异常处理机制。
finally是对Java异常处理模型的最佳补充。finally结构使代码总会执行,而不管无异常发生。使用finally可以维护对象的内部状态,并可以清理非内存资源。特别实在关闭数据库连接这方面,如果程序员把数据库连接的close()方法放到finally中,就会大大降低程序出错的几率。
finalize:Java中的一个方法名
Java技术使用finalize()方法在垃圾收集器将对象从内存中清除出去前,做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没被引用时对这个对象调用的。它是在Object类中定义的,因此所的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

& 和 && 的区别?

按位与:a&b是把a和b都转换成二进制数然后再进行与的运算;
逻辑与:a&&b就是当且仅当两个操作数均为 true时,其结果才为 true,只要有一个为false,a&&b就为false。
&&进行的是短路判断,即如果左侧的表达式的结果为false,整个的结果就为false,不再计算右侧的表达式的值。
||与|的区别
1,&&和||是短路运算符,&和|是非短路运算符。
2,&&与&区别:两者都表示“与”运算,但是&&运算符第一个表达式不成立的话,后面的表达式不运算,直接返回。而&对所有表达式都得判断。
3,|| 与|区别:两者都表示“或”运算,但是||运算符第一个表达式成立的话,后面的表达式不运算,直接返回。而|对所有表达式都得判断。

Java 中的参数传递时传值呢?还是传引用?

值传递
Java 函数在传递引用数据类型时,也只是拷贝了引用的值罢了,之所以能修改引用数据是因为它们同时指向了一个对象,但这仍然是按值调用而不是引用调用。

Java 中的 Math.round(-1.5) 等于多少?

-1,四舍五入。

如何实现对象的克隆?

在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

在深克隆中,除了对象本身被复制外,对象所包含的所有

深克隆和浅克隆的区别?
浅克隆:克隆出来的数据并不能完全脱离原数据,克隆前与克隆后的变量各自的变化会相互影响。这是因为引用变量存储在栈中,而实际的对象存储在堆中。每一个引用变量都有一根指针指向其堆中的实际对象。即当一个变量值改变时,另一个变量也会跟着发生变化。

深克隆:所有元素或属性均完全复制,与原对象完全脱离,也就是说所有对于新对象的修改都不会反映到原对象中。这是因为原始变量之间的赋值操作本质上就是当一个原始变量把值赋给另一个原始变量时,只是把栈中的内容复制给另一个原始变量,在这种操作下,引用变量指向的将不再是堆中的同一块地址,因此对于新对象的修改并不会影响到原对象。

什么是 Java 的序列化,如何实现 Java 的序列化?

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。

将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

概念

序列化:把Java对象转换为字节序列的过程。

反序列化:把字节序列恢复为Java对象的过程。

Java 中的反射是什么意思?有哪些应用场景?

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。

解释下什么是面向对象?面向对象和面向过程的区别?

面向过程:面向过程是一种以过程为中心的编程思想,其原理就是将问题分解成一个一个详细的步骤,然后通过函数实现每一个步骤,并依次调用。

面向对象:面向对象则是一种以对象为中心的编程思想,通过分析问题,分解出一个一个的对象,然后通过不同对象之间的调用来组合解决问题。

总而言之,就是两者的关注点不同,一个是面向过程,一步一步解决问题,另一个是面向对象,通过让对象进行一些行为来解决问题。

hashCode()与equals()的相关规定:

如果两个对象相等,则hashcode一定也是相同的

hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值

两个对象相等,对两个equals方法返回true

两个对象有相同的hashcode值,它们也不一定是相等的

综上,equals方法被覆盖过,则hashCode方法也必须被覆盖

hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

==与equals的区别

==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同

==是指对内存地址进行比较 equals()是对字符串的内容进行比较

线程

有使用过线程么?

业务中经常会使用到,创建线程可以继承thread ,实现runnable,还有线程池。可以防止阻塞,提高cpu利用率。线程池可以管理监控线程,控制最大并发数,避免多线程竞争,无限创建,导致应用内存溢出,重复利用,可以回收复用,避免频繁创建销毁,浪费资源。

线程池

底层实现,是队列;底层源码实现类为ThreadPoolExecutor这个类中一个参数就是阻塞队列。

将待处理任务放入阻塞队列,然后在线程创建后执行队列中任务。如果任务超出线程数量,则等线程执行完之后再从队列中取出,这样的思想。

为何要使用线程池

管理线程:线程池可以统一分配线程,管理和监控。

控制最大并发数:所以创建一个线程池是个更好的解决方案,因为可以更好控制线程的数量,避免过多线程竞争。而不是无限的创建,可能导致应用内存溢出。

线程复用:可以回收再利用这些线程避免频繁的创建和销毁。提高资源使用率。

线程池底层实现,七大参数

corePoolSize: 线程中核心线程数

maximumPoolSize:最大线程数。最大上限,当常用线程池线程满了,阻塞队列中任务也满了,还有任务过来,则这时可以扩容,临时加班到最大上限。

keepalive: 多余线程存活时间,也就是临时线程存活的时间,比如临时加班一个小时后,执行完任务,就下班。从max退回到核心线程数

unit 时间单位

workQueue:阻塞队列,待执行存放任务区域。

threadFatory:线程工厂,一般默认

拒绝策略:窗口最大数量满了和阻塞队列也满了,那么我们就拒绝一些任务。

线程池的几个核心参数?

队列,线程中核心线程数 ,最大线程数,临时线程数存活时间

线程状态?

新建,就绪,运行,阻塞,死亡

线程池任务执行的主要工作流程:

线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
当执行 execute() 方法添加一个任务时,线程池会判断:(a) 若正在运行的线程数量小于 corePoolSize(核心池大小;) 值,则立刻创建线程运行此任务;(b) 若正在运行的线程数量大于或等于 corePoolSize 值,则将此任务放入队列;© 若此时队列满了,而且正在运行的线程数量小于 maximumPoolSize (最大线程数) 值,则创建非核心线程立刻运行这个任务;(d) 若队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize 值,那么线程池会执行设置的拒绝策略。当一个线程完成任务时,它会从队列中取下一个任务来执行。当一个线程空闲时,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize 值,那么这个线程会被销毁。

sleep() 方法和 wait() 方法区别?

都造成线程阻塞。
释放锁:sleep 方法没有释放锁,而 wait 方法释放了锁 。
作用:Wait 通常被用于线程间通信,sleep 用于暂停执行。
苏醒:wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。

多线程有什么用?

单核:比如一个线程使用的时候CPU计算,IO空闲,IO操作时候CPU空闲,当CPU空闲时,执行另一个线程IO操作对方迟迟没有返回,这时CPU就空闲,而用户阻塞。为了防止阻塞,我们开启多线程。这样提高CPU利用率。

多核,提高CPU利用率,多核分别执行多个线程。比如一个复杂任务,让多核并行,提高效率。

run()方法和start()方法有和区别

每个线程都是通过某个特定的Thread对象对于的run()方法来完成其操作的,run方法称为线程体,通过调用Thread类的start方法来启动一个线程。start()方法用于启动线程,run()方法用于执行线程的运行代码,run()可以反复调用,而start()方法只能被调用一次。start()方法来启动一个线程,真正实现了多线程的运行。调用start()方法无需等待run()方法体代码执行结束,可以直接继续执行其它的代码;调用start()方法线程进入就绪状态,随时等该CPU的调度,然后可以通过Thread调用run()方法来让其进入运行状态,run()方法运行结束,此线程终止,然后CPU再调度其它线程。

为什么调用start()方法会执行run()方法,为什么不能直接调用run()方法

这是一个常问的面试题,new Thread,线程进入了新建的状态,start方法的作用是使线程进入就绪的状态,当分配到时间片后就可以运行了。start方法会执行线程前的相应准备工作,然后在执行run方法运行线程体,这才是真正的多线程工作。

如果直接执行了run方法,run方法会被当作一个main线程下的普通方法执行,并不会在某个线程中去执行它,所以这并不是多线程工作。

小结:

调用start方法启动线程可使线程进入就绪状态,等待运行;run方法只是thread的一个普通方法调用,还是在主线程里执行。

什么是多线程上下文切换?

CPU在执行一个已经运行的线程时候切换到另一个在等待获取CPU执行权的线程。,当切换执行权的时候就叫做多线程上下文切换。消耗大量的 CPU 时间

创建线程的方式

继承Thread类

实现Runnable接口

通过Callable和Future创建线程JDK5升级

创建线程池(当前最普遍)

集合

什么是集合

集合就是一个放数据的容器,准确的说是放数据对象引用的容器

集合类存放的都是对象的引用,而不是对象的本身

集合类型主要有3种:set(集)、list(列表)和map(映射)。

集合和数组的区别

数组是固定长度的;集合可变长度的。

数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。

数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。

HashSet如何检查重复?HashSet是如何保证数据不可重复的?

向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles 方法比较。HashSet 中的add ()方法会使用HashMap 的put()方法。

HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复( HashMap 比较key是否相等是先比较hashcode 再比较equals )。
HashMap 基于 Hash 算法实现的
当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标,存储时,如果出现hash值相同的key,此时有两种情况。
(1)如果key相同,则覆盖原始值;
(2)如果key不同(出现冲突),则将当前的key-value放入链表中
获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)

什么是TreeMap 简介

TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。

TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。

TreeMap是线程非同步的。

如何决定使用 HashMap 还是 TreeMap?

对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。

框架

Spring,SpringBoot,SpringCloud,SpringMVC

String是一个轻量级的框架,方便解耦,简便开发,提供AOP和IOC,切面和控制反转,创建和管理对象,支持集成优秀的框架

StringBoot 是一个服务于框架的框架,简化新的spring应用的初始搭建,内嵌

servlet容器,可以以jar包的形式对立运行,让文件配置变得简单。可以快速开启一个web容器进行开发。Spring Boot 最大的特点是无需 XML 配置文件,能自动扫描包路径装载并注入对象,并能做到根据 classpath 下的 jar 包自动配置。

SpringCloud 是一个完成的微服务框架,基于Springboot,像一个大的容器,把微服务框架集成起来,是一系列框架的有序集合,减少开发成本。

核心组件:

Spring Cloud Eureka:服务注册与发现

Spring Cloud Zuul:服务网关

Spring Cloud Ribbon:客户端负载均衡

Spring Cloud Feign:声明性的Web服务客户端

Spring Cloud Hystrix:断路器

Spring Cloud Confifig:分布式统一配置管理

Eurka 工作流程:

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 将实例从注册表中删除

Zuul网关的作用

网关有以下几个作用:

统一入口:为服务提供一个唯一的入口,网关起到外部和内部隔离的作用,保障了后台服务的安全性。

鉴权校验:识别每个请求的权限,拒绝不符合要求的请求。

动态路由:动态的将请求路由到不同的后端集群中。

减少客户端与服务端的耦合:服务可以独立发展,通过网关层来做映射

SpringIOC

IoC(Inverse of Control:控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。IOC思想是基于IOC容器来完成的,IOC容器底层就是对象工厂(BeanFactory接口)。IOC的原理是基于xml解析、工厂设计模式、反射来实现的。 IoC 容器实际上就是个Map(key,value)Map 中存放的是各种对象。

通俗易懂的一句话结论:之前需要我们自己手动new对象的,但是我们现在不需要反复去new对象了,而是把new对象的主动权交给IOC容器,我们什么时候用什么时候取就可以了。随着技术越来越先进,在Spring原生中我们觉得xml文件中配置还是有点麻烦,后来就有了springboot内部集成了这些功能,我们直接一个注解就可以了,方便了很多。

SpringAOP

AOP(Aspect-Oriented Programming:面向切面编程)AOP代表的是一个横向的关系,剖开对象的内部,并且把影响多个类的共同行为抽取出来,作为公共模块(叫做切面Aspect),然后再通过织入的方式把这个切面放进去。理解来说:就是能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

通俗易懂的一句话结论:就是不通过修改源代码方式,在主干功能里面添加新功能。

AOP底层是通过动态代理来实现的,同时有JDK动态代理和CGLIB动态代理两种方式:

1.有接口的情况,使用 JDK 动态代理,即创建接口实现类代理对象,增强类的方法。

2.没有接口的情况,使用 CGLIB 动态代理,即创建子类的代理对象,增强类的方法。

Spring自动装配是什么?解决了什么问题?

自动装配就是让应用程序上下文为你找出依赖项的过程。说的通俗一点,就是Spring会在上下文中自动查找,并自动给bean装配与其关联的属性!

spring中实现自动装配的方式有两种,一种是通过xml文件、另一种是通过注解。

Spring自动装配解决了Spring时代项目配置繁琐的问题,简化配置。

自动装配实现原理:

当启动Springboot应用程序的时候,会先创建SpringApplication对象,在对象的构造方法中会进行某些参数的初始化工作,最主要的是判断当前应用程序的类型以及初始化器和监听器,在这个过程中会加载整个应用程序中的Spring.factories文件,将文件内容放到缓存对象中,方便后续获取。

mysql

什么是事务的四大特性(ACID)?

原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用

一致性: 事务执行前后,数据保持一致,多个事务对同一个数据读取的结果是相同的

隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的

持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

索引失效的几种情况?

1)like 以%开头,索引无效;当like前缀没有%,后缀有%时,索引有效;

2)or 语句前后没有同时使用索引。当 or 左右查询字段只有一个是索引,该索引失效,只有左右查询字段均为索引时,才会生效;

3)联合索引不使用第一列,索引失效;

4)数据类型出现隐式转化。如 varchar 不加单引号的话可能会自动转换为 int 型,使索引无效,产生全表扫描;

5)在索引列上使用 IS NULL 或 IS NOT NULL操作。最好给列设置默认值。

6)在索引字段上使用not,<>,!=。不等于操作符是永远不会用到索引的,因此对它的处理只会产生全表扫描。 优化方法: key<>0 改为 key>0 or key<0。

7)对索引字段进行计算操作、字段上使用函数。

8)当 MySQL 觉得全表扫描更快时(数据少);

mysql隔离级别

1.Read Uncommitted(读未提交)

在一个事务处理过程里读取了另一个未提交的事务中的数据。会导致脏读。

2.Read Committed(读已提交)

这是大多数数据库系统的默认隔离级别,但不是MySQL默认的。会导致不可重复读,事务a读取数据,事务b立马修改了这个数据并且提交事务给数据库,事务a再次读取这个数据就得到了不同的结果。

3.Repeatable Read(可重读)

这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。会导致幻读,InnoDB和Falcon存储引擎通过多版本并发控制机制解决了该问题。

4.Serializable(可串行化)(更高级别隔离,避免脏读,避免不可重复读,避免幻读)

这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

spring bean 容器的生命周期流程如下:

Spring Bean 的生命周期指的是从一个普通的 Java 类变成 Bean 的过程.

普通 Java 类 -> BeanDefinition -> Spring Bean 以注解情况说明,就是 Spring 会扫描指定包下面的 Java 类,然后将其转化成 BeanDefinition 对象,然后 Spring 会根据 BeanDefinition 来创建 bean(主要一点就是 Spring 是根据 BeanDefinition 来创建 Bean)

Spring Boot 最最最核心的注解

用在 Spring Boot 主类上,标识这是一个 Spring Boot 应用,用来开启 Spring Boot 的各项能力。其实这个注解就是 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 这三个注解的组合,也可以用这三个注解来代替 @SpringBootApplication 注解。

redis

Redis

基于内存;

单线程减少上下文切换,同时保证原子性;

IO多路复用;

高级数据结构(如 SDS、Hash以及跳表等)。

为何使用单线程?

因为 Redis 是基于内存的操作,CPU 不会成为 Redis 的瓶颈,而最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。

详细原因

1)不需要各种锁的性能消耗

Redis 的数据结构并不全是简单的 Key-Value,还有 List,Hash 等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在hash当中添加或者删除一个对象。这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。

2)单线程多进程集群方案

单线程的威力实际上非常强大,每核心效率也非常高,多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需要了,需要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的。

所以单线程、多进程的集群不失为一个时髦的解决方案。

什么是分布式锁?

要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁、进程锁。线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。

分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问

先删后写还是先写后删?

先删缓存后写 DB

产生脏数据的概率较大(若出现脏数据,则意味着再不更新的情况下,查询得到的数据均为旧的数据)。

比如两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还一直这样脏下去了。

先写 DB 再删缓存

产生脏数据的概率较小,但是会出现一致性的问题;若更新操作的时候,同时进行查询操作并命中,则查询得到的数据是旧的数据。但是不会影响后面的查询。

比如一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后之前的那个读操作再把老的数据放进去,所以会造成脏数据。

Redis 如何保证原子性?

因为 Redis 是单线程的,所以 Redis 提供的 API 也是原子操作。
但我们业务中常常有先 get 后 set 的业务常见,在并发下会导致数据不一致的情况。可以使用 incr、decr、setnx 等原子操作;
2)客户端加锁;
3)使用 Lua 脚本实现 CAS 操作。

Redis如何实现高可用,部署方式?

部署方式有主从,哨兵,集群。主从读写分离,但是主服务器挂了之后无法自动切换,需要手动处理,从数据库里选一个作为主。哨兵在主从的基础上加了监测,每个节点由哨兵监控,每隔断时间就检查主服务器是否能连接。如果半数哨兵都检查了连接不上,就从从服务器里选个master。集群是多个主,多个从。

Redis 缓存雪崩,缓存击穿,缓存穿透

正常使用缓存时,一个请求过来,先在缓存查询一下是否存在信息,如果存在直接返回,如果不存在,连接数据库,查询出的数据更新到缓存,以备下次查询。

缓存穿透:
一个请求过来在缓存里也查不到信息,在数据库也查不到信息,查不到信息就不会更新缓存,如果恶意请求的话,数据库压力会很大。解决:缓存中
存空数据,或者业务接口上拦截非法参数。

缓存雪崩:

大量key的过期时间比较相近,在某一时间大面积失效,数据库突然接收很大量的请求,会导致崩溃。解决:key过期时间后面加随机数,让缓存key均匀失效,不在同一时间段。

缓存击穿:

单个热点key失效,类似雪崩,热点key可以不设置过期时间,或者分布式锁解决。

mq

什么是消息队列MQ(Message Queue)

消息(Message): 传输的数据。

队列(Queue):队列是一种先进先出的数据结构。

消息队列从字面的含义来看就是一个存放消息的容器。

消息队列可以简单理解为:把要传输的数据放在队列中。

把数据放到消息队列叫做生产者

从消息队列里边取数据叫做消费者

消息队列是一种异步的服务间通信方式,是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削 锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。

消息中间件的组成部分

1 Broker
消息服务器,作为server提供消息核心服务
2 Producer
消息生产者,业务的发起方,负责生产消息传输给broker,

3 Consumer

消息消费者,业务的处理方,负责从broker获取消息并进行业务逻辑处理

4 Topic

主题,发布订阅模式下的消息统一汇集地,不同生产者向topic发送消息,由MQ服务器分发到不同的订阅者,实现消息的广播

5 Queue

队列,PTP模式下,特定生产者向特定queue发送消息,消费者订阅特定的queue完成指定消息的接收
6 Message
消息体,根据不同通信协议定义的固定格式进行编码的数据包,来封装业务数据,实现消息的传输

消息中间件的工作流程

【1】发送端 MQ-Product (消息生产者)将消息发送给 MQ-server;

【2】MQ-server 将消息落地,持久化到磁盘等;

【3】MQ-server 回 ACK 给 MQ-Producer;

【4】MQ-server 将消息发送给消息接收端 MQ-Consumer (消息消费者);

【5】MQ-Consumer 消费接收到消息后发送 ACK 给 MQ-server;

【5】MQ-server 将落地消息删除;

ACK——消息确认机制

消息的重发,补发策略

为了保证消息必达,MQ使用了消息超时、重传、确认机制。使得消息可能被重复发送,当消息生产者收不到 MQ-server 的ACK,重复向 MQ-server发送消息。MQ-server 收不到消息消费者的 ACK,重复向消息消费者发消息。

消息重复发送产生的后果

对于非幂等性的服务而言,如果重复发送消息就会产生严重的问题。譬如:银行取钱,上游支付系统负责给用户扣款,下游系统负责给用户发钱,通过MQ异步通知。不管是上游的ACK丢失,导致 MQ收到重复的消息,还是下半场 ACK丢失,导致系统收到重复的出钱通知,都可能出现,上游扣了一次钱,下游发了多次钱。 消息队列的异步操作,通常用于幂等性的服务,非幂等性的服务时不适用中间件进行通信的。更多的是建立长连接 Socket 进行通信的。

MQ内部如何做到幂等性的

对于每条消息,MQ内部生成一个全局唯一、与业务无关的消息ID:inner-msg-id。当 MQ-server 接收到消息时,先根据 inner-msg-id 判断消息是否重复发送,再决定是否将消息落地到 DB中。这样,有了这个 inner-msg-id 作为去重的依据就能保证一条消息只能一次落地到 DB。

消息消费者应当如何做到幂等性

【1】对于非幂等性业务且要求实现幂等性业务:生成一个唯一ID标记每一条消息,将消息处理成功和去重日志通过事物的形式写入去重表。

【2】对于非幂等性业务可不实现幂等性的业务:权衡去重所花的代价决定是否需要实现幂等性,如:购物会员卡成功,向用户发送通知短信,发送一次或者多次影响不大。不做幂等性可以省掉写去重日志的操作。

如何保证消息队列的高可用?

RabbitMQ:镜像集群模式

RabbitMQ 是基于主从做高可用性的,Rabbitmq有三种模式:单机模式、普通集群模式、镜像集群模式。单机模式一般在生产环境中很少用,普通集群模式只是提高了系统的吞吐量,让集群中多个节点来服务某个 Queue 的读写操作。那么真正实现 RabbitMQ 高可用的是镜像集群模式。

镜像集群模式跟普通集群模式不一样的是,创建的 Queue,无论元数据还是Queue 里的消息都会存在于多个实例上,然后每次你写消息到 Queue 的时候,都会自动和多个实例的 Queue 进行消息同步。这样设计,好处在于:任何一个机器宕机不影响其他机器的使用。坏处在于:1. 性能开销太大:消息同步所有机器,导致网络带宽压力和消耗很重;2. 扩展性差:如果某个 Queue 负载很重,即便加机器,新增的机器也包含了这个 Queue 的所有数据,并没有办法线性扩展你的 Queue。

CAS与Synchronized

cas机制属于乐观锁,不加锁,先改。当线程把变量完成变更保存前,会比较要修改的那个值跟内存地址当前的值是不是同一个 ,如果是,说明没人更新过这个内容,就把修改后 的值存进去提交,否则就提交失败,失败后可以进行重试,重试的这个过程叫自旋。

Synchronized属于悲观锁,先锁改数据。

cas会发生ABA问题,解决方案加版本。

悲观锁:总是假设最坏的情况,每次拿到数据时都会上锁,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下是否有人更新过这个数据
乐观锁是一种思想。CAS是这种思想的一种实现方式。CAS(compare and swap)比较和替换,如果是预期的值,则替换,如果不是,则什么都不做返回false。是非阻塞算法的一种常见实现。当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,但失败的线程不会像悲观锁那样阻塞,而是被告知这次竞争中失败,并可以再次尝试。

什么是CAS(compare and set)

简单来讲就是比较,交换。CAS是这种思想的一种实现方式。CAS(compare and swap)比较和替换,如果是预期的值,则替换,如果不是,则什么都不做返回false。类似于github版本号,当我比较的时候如果一样那么我就可以提交修改,如果不一样有冲突,那么我就要重新获得主物理内存真实值。

分布式事务

分布式事务:

二阶段提交

在分布式系统中,每个节点虽然可以知晓自己的操作成功或失败,但不知晓其他节点的成功或失败
第一阶段请求阶段
1 协调者(Coordinator,即事务管理器)会向事务的参与者(Cohort,即本地资源管理器)发起执行操作的 CanCommit 请求,并等待参与者的响应.
2 参与者接收到请求后,会执行请求中的事务操作,记录日志信息(包含事务执行前的镜像),同时锁定当前记录。参与者执行成功,则向协调者发送“Yes”消息,表示同意操作;若不成功,则发送“No”消息,表示终止操作。
3 当所有的参与者都返回了操作结果(Yes 或 No 消息)后,系统进入了提交阶段。
第二阶段提交阶段
协调者会根据所有参与者返回的信息向参与者发送 DoCommit 或 DoAbort 指令
若协调者收到的都是“Yes”消息,则向参与者发送“DoCommit”消息,参与者会完成剩余的操作并释放资源,然后向协调者返回“HaveCommitted”消息;
如果协调者收到的消息中包含“No”消息,则向所有参与者发送“DoAbort”消息,此时发送“Yes”的参与者则会根据之前执行操作时的回滚日志对操作进行回滚,然后所有参与者会向协调者发送“HaveCommitted”消息;
协调者接收到“HaveCommitted”消息,就意味着整个事务结束了。
二阶段提交的实现 - XA
三阶段提交,也叫三阶段提交协议(英文缩写3pc)

是在计算机网络及数据库的范畴下,使得一个分布式系统内的所有节点能够执行事务的提交的一种分布式算法。三阶段提交是为解决两阶段提交协议的缺点而设计的。

TCC:try,confirm,cancel

tcc补偿机制
try:预处理 - 完成所有的业务检查(一致性),预留必须业务资源(准隔离性)锁定资源
confirm:确认(如果成功了,执行confirm)
cancel:撤销(若果失败了,执行cancel)

缺点:1.补偿代码复杂 2.因为补偿需要保证幂等性 - 乐观锁
优点:不需要一致阻塞 - 快

seate

1.seate AT 与XA的区别

本质上都是2pc,但是XA是在数据库,AT是在业务层

AT多了一个TC

TM负责决议,TC负责通知RM,RM负责执行

其他

oauth2.0

Oauth2.0 是一种授权协议,有授权码模式,密码模式,客户端模式,授权码模式:资源拥有者想通过第三方系统授权,且不需要输入用户名密码达到登录某客户端,客户端拉起授权页面,用户点击授权,第三方系统生成请求code,重定向回来,客户端携带授权码,clientID,clientSercet向授权服务器获取访问令牌accesstoken。拿到令牌客户端可以请求第三方系统的API。密码模式:内部的系统,或者母子系统,资源拥有者提供用户名密码进行登录。客户端模式:客户端直接调用第三方系统

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值